In my program (MVVM WPF) there are lot of Enumerations, I am binding the enums to my controls in the view.
There are lot of ways to do it.
1) To bind to ComboBoxEdit(Devexpress Control). I am using ObjectDataProvider.
and then this
<dxe:ComboBoxEdit ItemsSource="{Binding Source={StaticResource SomeEnumValues}>
This works fine but in TabControl header it doesn't.
2) So, I thought of using IValueConverter that didnt worked either.
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (!(value is Model.MyEnum))
{
return null;
}
Model.MyEnum me = (Model.MyEnum)value;
return me.GetHashCode();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
on XAML:
<local:DataConverter x:Key="myConverter"/>
<TabControl SelectedIndex="{Binding Path=SelectedFeeType,
Converter={StaticResource myConverter}}"/>
3) The third way of doing this is to make a behavior dependency property
Something like this
public class ComboBoxEnumerationExtension : ComboBox
{
public static readonly DependencyProperty SelectedEnumerationProperty =
DependencyProperty.Register("SelectedEnumeration", typeof(object),
typeof(ComboBoxEnumerationExtension));
public object SelectedEnumeration
{
get { return (object)GetValue(SelectedEnumerationProperty); }
set { SetValue(SelectedEnumerationProperty, value); }
}
I want to know what is the best way to handle enumerations and binding to it. Right now I am not able to bind tabheader to the enums.
Here's a nicer way of doing it:
On your model, put this Property:
public IEnumerable<string> EnumCol { get; set; }
(Feel free to change the name to whatever suits you, but just remember to change it everywhere)
In the constructor have this (or even better, put it in an initialization method):
var enum_names = Enum.GetNames(typeof(YourEnumTypeHere));
EnumCol = enum_names ;
This will take all the names from your YourEnumTypeHere and have them on the property you'll be binding to in your xaml like this:
<ListBox ItemsSource="{Binding EnumCol}"></ListBox>
Now, obviously, it doesn't have to be a ListBox, but now you're simply binding to a collection of strings, and your problem should be solved.
Related
I have the an ICollectionView in my ViewModel:
public class ContactsPanelViewModel : ViewModelBase
{
private ICollectionView accountContactsView;
public ICollectionView AccountContactsView
{
get => accountContactsView;
set => NotifyPropertyChangedAndSet(ref accountContactsView, value);
}
public ContactsPanelViewModel()
{
AccountContactsView = CollectionViewSource.GetDefaultView(G.AccountContacts); //G.AccountContacts is an IEnumarable<Contact> object
AccountContactsView.SortDescriptions.Add(new SortDescription("FullName_LastNameFirst", ListSortDirection.Ascending));
AccountContactsView.CurrentChanged += NewContactSelected;
AccountContactsView.MoveCurrentToFirst();
}
}
This is the list it is bound to:
<ListView
x:Name="ContactList"
HorizontalAlignment="Left"
ItemsSource="{Binding Path=AccountContactsView, Converter={StaticResource ContactCardConverter}}"
IsSynchronizedWithCurrentItem="True"/>
And this is the converter I created:
public class ContactCardConverter : IValueConverter
{
public object Convert(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ListCollectionView)
{
List<ContactCard> contactCards = new List<ContactCard>(); //ContactCard is a UserControl
ListCollectionView data = Value as ListCollectionView;
if (data.Count > 0 && data.GetItemAt(0) is Contact)
{
foreach(Contact contact in data)
{
contactCards.Add(new ContactCard(contact));
}
return contactCards;
}
else
{
return null;
}
}
else
{
return null;
}
}
public object ConvertBack(object Value, Type TargetType, object Parameter, CultureInfo Culture)
{
if (Value is ContactCard)
{
return (Value as ContactCard).Contact;
}
else
{
return null;
}
}
}
The issue is that because I am using a converter the CurrentItem in my ICollectionView isn't being synchronized when the ListView selection changes. The converter is definitely the issue since removing the converter makes it work perfectly.
I added in a ConvertBack function thinking it would call that if there was a converter used but it didn't work.
I could theoretically make the ICollectionView of the type ContactCard (which is a UserControl), but that doesn't seem like it is how it should be done since from what I understand the ViewModel shouldn't be dependent on how the View will look. This would also add some complecity as I would need to keep the ICollectionView in sync with my actual collection of Contact objects.
What is the proper way to synchronize a ListView and an ICollectionView when the ListView uses a converter?
The proper way is don't use a converter.
You shouldn't convert your viewmodel-CollectionView to the List(as in your case) or another CollectionView in converter, because otherwise in case you created a CollectionView in converter you will synchronize your view (and IsSynchronizedWithCurrentItem works good) against in converter created collection. List can't be synchronized, because it does not implement IColectionView and has not CurrentItem.
The proper way is don't use a converter and put ContactCard-UserControl to the ListView.ItemTemplate.
<ListView.ItemTemplate>
<DataTemplate>
<youCustomCtlNameSpace:ContactCard Contact="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
If you mandatory wants to use a converter, then IsSynchronizedWithCurrentItem makes no sense and you have to synchronize V and VM by yourself via binding with converter to ListView.SelectedItem and your Contact have to hold reference to the UI-object(It's MVVM conform, if you have no dependencies to View see: Is MVVM pattern broken?). But as I already noted it's not a preferred way!
I have a TextBlock as follow:
<TextBlock Text="You don't have any more items." Visibility="{binding}"
and in code behind I defined a Stack called items as follow:
private Stack<Item> _items;
How do I bind the text visibility in xaml to visible when _item.Any is false?
There are several steps to achieving what you want to do and they are all described here
You need to create a value converter similar to this;
public class EmptyCollectionToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = (Stack<int>) value;
return collection.Any() ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you need to add a reference to is in your resource dictionary in your xaml like this;
<views:EmptyCollectionToVisibilityConverter x:Key="EmptyCollectionToVisibilityConverter"/>
Finally bind your property in your view model to the visibility of your control and give the binding the converter like this;
Visibility="{Binding Items, Converter={StaticResource EmptyCollectionToVisibilityConverter}}"
Your property will probably need to be an observableCollection (which will mean changing the value converter example I gave you slightly.
I'd probably go with:
private Stack<Item> _items;
// bind to this property using converter
public bool IsVisible => !(_items?.Any(...) ?? false);
You shouldn't expose your _stack directly, but e.g. use methods to do something (because you need to rise notification every time you push/pop an item):
public void PushItem(Item item)
{
_items.Push(item);
OnPropertyChanged(nameof(IsVisible)); // implement INotifyPropertyChanged
}
Scenario: As an Embedded Software Engineer, I want my MVVM View to set multiple properties in my XAML style from a single binding to my ViewModel.
Given: My view contains a Style that is applied to a Button, and my ViewModel has a 'key' property.
Relevant bits of the ButtonStyle.xaml ResourceDictionary:
<!-- This is the style used for Buttons -->
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<!-- Other, common Style setters live here -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="ButtonBorder">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Path=Key,
Converter={r:ButtonImageConverter}}"/>
<Label Grid.Column="1"
Content="{Binding Path=Key,
Converter={r:ButtonTextConverter}}"/>
</Grid>
</Border>
<!-- ControlTemplate.Triggers and other stuff -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Relevant bits of the CommandViewModel.cs class:
// The relevant bits of the ViewModel class that the button is bound to
public class CommandViewModel: INotifyPropertyChanged, ICommand
{
private String Key;
{
get { return key; }
set { OnPropertyChanged(ref key, value); }
}
// ... Plus implementation of OnPropertyChanged, ICommand, etc.
}
When: The Button is bound to the ViewModel object.
The binding from the ViewModel to the Button:
<Button Style="{DynamicResource ButtonStyle}"
DataContext="{Binding Path=CloseCommand}"
Content="{Binding Path=Key}"
Command="{Binding Path=Command}"
CommandParameter="{Binding}"/>
Then: Several of the Style properties are set (using Value Converters) based on a collection of Resources associated with the 'key' property.
Incomplete implementation of one of the Value Converters (the other is very similar and not included here):
public class ButtonTextConverter : MarkupExtension, IValueConverter
{
// This is just so that I can reference it in the XAML as:
// Converter={r:ButtonImageConverter}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
String index = value as String;
if (String.IsNullOrEmpty(index))
{
throw new ArgumentException("The name to convert cannot be Null or Empty", "value");
}
// The code gets here just fine, with index containing the same
// string as the CommandViewModel.Key property.
// But ...
// How can I find a the String Resouce, using the index, when
// there may be several all related to the same Control? I.e.
// the ButtonImageConverter will also find a String containing
// the URI path for the image to display on the Button.
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return String.Empty;
}
}
Architecturally, my View is an Assembly that contains all of my XAML (no code behind for any of the XAML components) and resources. The ViewModel is in a different assembly and knows nothing about resources, images, localised text, cultures, etc.
One solution, which would break my architecture, would be to add more properties to the ViewModel class and let it pull the various values from an XML file. I don't like this because then my Model and ViewModel are both polluted by View stuff. I want to keep all that stuff in the View.
Another solution that I've looked at it using custom resource classes. The ResourceManager.GetObject Method page contains some example code for how to create a .RESX resource file that contains custom resources. I've tried this, but the result is beyond my current comprehension, and it's not in my customer's best interest for me to try to lean how to use the Microsoft ResX schema. However, this may be the way to go, if there's an easy way to save complex objects (that the Value Converters could use to de-multiplex the Key) as a single Resource entity.
What options do I have to allow different resources to be uniquely identified by the same identification 'key', depending on which Value Converter is trying to find the Resource?
The solution I've implemented is a little crude and relies on giving the resources different names, formed by appending the property name to the resource 'Key' name. I've encapsulated that into a ButtonResources class that the ValueConverter classes use as a helper to get the actual resources.
internal class ButtonResources
{
#region Static Fields
private static ResourceManager resourceManager =
new ResourceManager("MyApplication.Resources.ButtonResources",
typeof(ButtonResources).Assembly);
private static Dictionary<String,ButtonResources> resourceDictionary =
new Dictionary<String, ButtonResources>();
#endregion
#region Instance Properties
internal String Text { get; set; }
internal BitmapImage Image { get; set; }
internal SolidColorBrush Brush { get; set; }
#endregion
#region Instance Fields
private String Name { get; set; }
#endregion
private ButtonResources(String buttonName)
{
this.Name = buttonName;
}
private void LoadResources(CultureInfo culture)
{
// Load the button Text
Text = resourceManager.GetString(Name + "Text", culture);
// Load the image file from a URI.
// See https://msdn.microsoft.com/en-us/library/aa970069(v=vs.100).aspx
// (It's regrettably complex, but there are lots of examples on t'interweb.)
String imageName = resourceManager.GetString(Name + "Image", culture);
Image = new BitmapImage(new Uri(#"pack://application:,,,/"
+ Assembly.GetExecutingAssembly().GetName().Name
+ ";component/Resources/"
+ imageName, UriKind.Absolute));
// Convert the colour resource to a suitable Brush object.
String colourName = resourceManager.GetString(Name + "Colour", culture);
Brush = new BrushConverter().ConvertFromString(colourName) as SolidColorBrush;
}
internal static ButtonResources FindButton(String buttonName, CultureInfo culture)
{
if (String.IsNullOrEmpty(buttonName))
{
throw new ArgumentException("The name of the button cannot be Null or Empty",
"buttonName");
}
ButtonResources buttonResources;
// Check whether the requested resource has been loaded yet.
if (resourceDictionary.ContainsKey(buttonName))
{
buttonResources = resourceDictionary[buttonName];
}
else
{
// Create a new instance with the requested name.
buttonResources = new ButtonResources(buttonName);
// Load the object's properties from the Resources.
buttonResources.LoadResources(culture);
// Add the new object to the dictionary.
resourceDictionary.Add(buttonName, buttonResources);
}
return buttonResources;
}
}
Each of the ValueConverter classes is simplified somewhat:
public abstract class ValueConverter
: MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public virtual object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return String.Empty;
}
}
public class ButtonTextConverter : ValueConverter, IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Use the ButtonResources helper to find the text to place on the button.
return ButtonResources.FindButton(value as String, culture).Text;
}
}
public class ButtonImageConverter : ValueConverter, IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Use the ButtonResources helper to find the image to put on the button.
return ButtonResources.FindButton(value as String, culture).Image;
}
}
public class ButtonColourConverter : ValueConverter, IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Use the ButtonResources helper to find the brush to use for the button.
return ButtonResources.FindButton(value as String, culture).Brush;
}
}
NOTE: In the Solution Explorer, I have cleared the Custom Tool field of the ButtonResources.resx file. This is because the ButtonResources class handles everything that the code created by the resgen.exe tool does.
I'm pretty sure that the CultureInfo stuff is going to go wrong if I ever need to localise the resources, but it works at the moment so I'll address that problem if it ever arises.
In addition to the localisation issue, the ButtonResources class could probably do with some extra code to handle cases where a named resource isn't present, whereby an acceptable default is provided, or at least something other than null. Again, this isn't causing a problem at the moment, so I've done nothing to fix it here.
I have a class like this.
public class ViewModel
{
public PengChat3ClientSock Sock { get; internal set; }
public ObservableCollection<Room> Rooms { get; internal set; }
public ViewModel(PengChat3ClientSock sock)
{
Sock = sock;
Rooms = new ObservableCollection<Room>();
}
}
and MainWindow.xaml.cs
public ObservableCollection<ViewModel> viewModel { get; set; }
(Of course it is initialized.)
And here is a constructor.
comboBox.ItemsSource = viewModel;
But here, i do not want to use viewModel, only viewModel.Sock.
How can i do this?
There is no viewModel.Sock, as viewModel is a collection of objects of type ViewModel, which contain that property.
Depending on your goals there are different solutions:
You can still bind comboBox to viewModel, but in the item template of the comboBox you can access the Sock property
You can create new collection that will contain only Sock objects ... but then you may have to make sure it is synchronized with the collection of ViewModel objects
Usually you would go about this in a slightly different way:
Create View (UserControl with all the ui elements you need)
Set your 'ViewModel' as DataContext on the newly created View (userControl.DataContext = viewModel)
In the view, where you define your combobox, you can now refer directly to the 'Sock' property on your viewmodel via Bindings.
Code:
<ComboBox ItemsSource="{Binding Sock}"/>
If you just want to set the ItemsSource from code-behind, which is kind of ugly, you might simply add the items in Sock to comboBox.Items - Alternatively you may need to create a new 'Binding' object from code-behind, but this is even uglier:
var binding = new Binding("Sock")
{
Source = viewModel
};
comboBox.ItemsSource = binding;
Please note that I haven't tested the 'Binding in code-behind approach', it's really an anti pattern to do it like that, especially if you're working with MVVM.
Also, you 'Sock' property is a class, and not a collection, so you won't really be able to do this; did you perhaps mean the 'Rooms' property of the ViewModel?
You can only bind an ItemsSource to a type which implements IEnumerable. I have run into this before, and made a converter to convert any object to a list. It's a simple and reusable solution, which leaves the ViewModel logic separated from the view logic:
Converter:
public class ItemToCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new List<object> { value };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<UserControl.Resources>
<local:ItemToCollectionConverter x:Key="ItemToCollectionConverter"/>
</UserControl.Resources>
...
<ComboBox ItemsSource="{Binding Sock, Converter={StaticResource ItemToCollectionConverter}}"/>
Tearing my hair out here! I have this type-converter:
class CouponBarcodeToVisibilityConverterColumn : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.IsInDesignMode)
{
if ((string)parameter == "123456")
{
return Visibility.Visible;
}
return Visibility.Hidden;
}
if (value == null)
{
return Visibility.Visible;
}
var barcodesWanted = ((string)parameter).Split(System.Convert.ToChar("_"));
var actualBarcode = (string)value;
return barcodesWanted.Any(barcodeWanted => barcodeWanted == actualBarcode) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
I have a UserControl with the following Resources section:
<UserControl.Resources>
<converters:CouponBarcodeToVisibilityConverterColumn x:Key="CouponBarcodeToVisibilityConverter1"/>
</UserControl.Resources>
I have a model called Bet, it looks like this:
public class Bet : INotifyPropertyChanged
{
//Lots of other stuff
private string _barcode;
public string Barcode
{
get { return _barcode; }
set
{
if (value == _barcode) return;
_barcode = value;
OnPropertyChanged("Barcode");
}
}
//Lots of other stuff
}
In the ViewModel which is the DataContext of my user control I have an Observable Collection of Bet. Back to my user control, I have a stack panel, the data context of which is the aforementioned Observable Collection.
Inside the Stack Panel I have a DataGrid, the ItemsSource property is simply {Binding}, deferring the binding up the tree as it were.
Inside my DataGrid I have this column:
<DataGridCheckBoxColumn x:Name="IsEwColumn" Binding="{Binding Wagers[0].IsEw,UpdateSourceTrigger=PropertyChanged}" Header="Each Way" Visibility="{Binding Path=Barcode, Converter={StaticResource CouponBarcodeToVisibilityConverter1}, ConverterParameter=123456}" Width="Auto"/>
The other element of the binding works perfectly (the checkbox is ticked whenever it is supposed to be) but my type converter is not. The breakpoint doesn't even get hit. The Barcode property inside Bet is definitely equal to 123456.
What have I missed?
What you have here is a list of bets for the items source of the data grid.
If you think about it
Bet1 could evaluate to visible when passed via type converter.
Bet2 could evaluate to visible when passed via type converter.
Bet3 could evaluate to collapsed when passed via type converter.
How would the datacolumn be both visible and collapsed at the same time.
You can't bind to visibility like that, unless you had an overall variable on the list or something that it could bind to.