This post will be a short post about how you can easily bind to nullable enums value in WPF. This is quite useful when you have optional values.
So lets start with a simple ViewModel shall we, it can be seen that this ViewModel uses a Nullable<ProductType> and that the ProductType itself uses the DescriptionAttribute
using System; using System.ComponentModel; using System.Runtime.CompilerServices; namespace WpfApplication1 { public enum ProductType { [Description("Wet Food")] WetFood=1, [Description("Dry Food")] DryFood=2 } public class MainWindowViewModel : INotifyPropertyChanged { private ProductType? selectedProductType ; public ProductType? SelectedProductType { get { return selectedProductType; } set { selectedProductType = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } }
All good so far, so now lets look at the XAML
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <ObjectDataProvider x:Key="ProductTypeEnumProvider" MethodName="GetValues" ObjectType="{x:Type sys:Enum}"> <ObjectDataProvider.MethodParameters> <x:Type TypeName="local:ProductType" /> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </Window.Resources> <Grid> <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" SelectedItem="{Binding SelectedProductType, Converter={x:Static local:NullableEnumConverter.Instance}, ConverterParameter={x:Static local:ProductType.DryFood}}"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=., Mode=OneWay, Converter={x:Static local:NullableEnumToFriendlyNameConverter.Instance}}" Height="Auto" Margin="0" VerticalAlignment="Center"/> </DataTemplate> </ComboBox.ItemTemplate> <ComboBox.ItemsSource> <CompositeCollection> <x:Static Member="local:NullHelper.NullComboStringValue"/> <CollectionContainer Collection="{Binding Source={StaticResource ProductTypeEnumProvider}}" /> </CompositeCollection> </ComboBox.ItemsSource> </ComboBox> </Grid> </Window>
Most of that is standard stuff. What is nice with my approach here is the use of the CompositeCollection which allows you to treat disparate sources as one overall source, in this case an empty string, and the actual enum values form the final ItemSource for the ComboBox
The empty string is within this small helper class
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WpfApplication1 { public class NullHelper { public static string NullComboStringValue { get { return "None"; } } } }
And there are also a couple of value converters that deal with the nullable enum value:
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Windows.Data; namespace WpfApplication1 { public class NullableEnumConverter : IValueConverter { private NullableEnumConverter() { } static NullableEnumConverter() { Instance = new NullableEnumConverter(); } public static NullableEnumConverter Instance { get; private set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) { return NullHelper.NullComboStringValue; } return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { Type enumType = parameter.GetType(); if (value.ToString().Equals(NullHelper.NullComboStringValue)) { return null; } object rawEnum = Enum.Parse(enumType, value.ToString()); return System.Convert.ChangeType(rawEnum, enumType); } } }
And also one that is responsible for showing the friendly name of the DescriptionAttribute that the enum values make use of:
using System; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; using System.Windows.Data; namespace WpfApplication1 { /// <summary> /// This class simply takes an enum and uses some reflection to obtain /// the friendly name for the enum. Where the friendlier name is /// obtained using the DescriptionAttribute, which hold the localized /// value read from the resource file for the enum /// </summary> [ValueConversion(typeof(object), typeof(String))] public class NullableEnumToFriendlyNameConverter : IValueConverter { private NullableEnumToFriendlyNameConverter() { } static NullableEnumToFriendlyNameConverter() { Instance = new NullableEnumToFriendlyNameConverter(); } public static NullableEnumToFriendlyNameConverter Instance { get; private set; } #region IValueConverter implementation /// <summary> /// Convert value for binding from source object /// </summary> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // To get around the stupid wpf designer bug if (value != null && !string.IsNullOrEmpty(value.ToString()) && !value.ToString().Equals(NullHelper.NullComboStringValue)) { FieldInfo fi = value.GetType().GetField(value.ToString()); // To get around the stupid wpf designer bug if (fi != null) { var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString(); } } return NullHelper.NullComboStringValue; } /// <summary> /// ConvertBack value from binding back to source object /// </summary> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new Exception("Cant convert back"); } #endregion } }
Hope that helps you in some small way. I know this post was a small one, but hopefully its useful
Rather than using ObjectDataProvider and CompositeCollection, you could use a markup extension:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
EnumValuesExtension.cs
hosted with ❤ by GitHub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sample.xaml
hosted with ❤ by GitHub
It makes for much cleaner XAML code…
Uh… Auto-embedding Gist links is a nice idea, but WordPress completely messed it up. Click the file name to see it on Gist in its original form.
Cool, like that too