I was at work the other day and I had a situation where I needed to use a nullable enum
. I wanted this to be available within a ComboBox
but It was not compulsary for the users to select something. I also wanted to allow the ComboBox
to print a friendly name of the Enum
value.
The friendly name thing is something I have covered before in a previous article Binding-and-Using-Friendly-Enums-in-WPF but I have never really come across a time where I needed to allow a ComboBox
with null
selection support for the user.
I did not want to pollute my enum
values with a special “None
” value, as that is really what the nullable aspect of it was doing. So I started to explore things a bit, and this is what I ended up with.
My ViewModel
and Enum
:
public enum Animals
{
[EnumMember(Value = "Its a nice kitty")]
Cat=1,
[EnumMember(Value = "Its a bow wow")]
Dog = 2
}
public class MainWindowViewModel : INPCBase
{
private Animals? selectedAnimal;
public MainWindowViewModel()
{
SelectedAnimal = null;
}
public Animals? SelectedAnimal
{
get { return this.selectedAnimal; }
set
{
RaiseAndSetIfChanged(ref this.selectedAnimal, value, () => this.SelectedAnimal);
MessageBox.Show(
"SelectedAnimal: " + (!selectedAnimal.HasValue ?
NullHelper.NullComboStringValue : selectedAnimal.ToString()));
}
}
}
Next up was my View
. The interesting points here are:
- We use a
ObjectDataProvider
to supply the enum
values - We use a
CompositeCollection
(which doesn’t work with Binding, i.e., ItemsSource
being bound) where we get the Enum
values and also a special “Null
” value from a helper class - We use a special
NullableEnumToFriendlyNameConverter
value converter to supply the friendly name lookup value for the enum
(providing it's not the special “Null
” value - We use a special
NullableEnumConverter
to convert back to either null
or a picked enum
value for the ViewModel
property setter
<Window x:Class="NullEnumCombo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:nullEnumCombo="clr-namespace:NullEnumCombo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="animalTypeFromEnum"
MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="nullEnumCombo:Animals" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<ComboBox Width="150" Height="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SelectedItem="{Binding SelectedAnimal,
Converter={x:Static nullEnumCombo:NullableEnumConverter.Instance},
ConverterParameter={x:Static nullEnumCombo:Animals.Cat}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Mode=OneWay,
Converter={x:Static
nullEnumCombo:NullableEnumToFriendlyNameConverter.Instance}}"
Height="Auto"
Margin="0"
VerticalAlignment="Center"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemsSource>
<CompositeCollection>
<x:Static Member="nullEnumCombo:NullHelper.NullComboStringValue"/>
<CollectionContainer
Collection="{Binding Source={StaticResource animalTypeFromEnum}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</Window>
Here are the 2 value converters and the small helper class:
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);
}
}
[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 =
(EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false);
return ((attributes.Length > 0) &&
(!String.IsNullOrEmpty(attributes[0].Value)))
?
attributes[0].Value
: value.ToString();
}
}
return NullHelper.NullComboStringValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new Exception("Cant convert back");
}
#endregion
}
public class NullHelper
{
public static string NullComboStringValue
{
get
{
return "(None)";
}
}
}