This is 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 let's 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 let's 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
{
[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
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && !string.IsNullOrEmpty(value.ToString())
&& !value.ToString().Equals(NullHelper.NullComboStringValue))
{
FieldInfo fi = value.GetType().GetField(value.ToString());
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;
}
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 it's useful.