Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / XAML

XAML: Binding To Nullable Enums With Friendly Names

0.00/5 (No votes)
3 Mar 2014CPOL1 min read 11.3K  
XAML: Binding to nullable enums with friendly names

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:

C#
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:

  1. We use a ObjectDataProvider to supply the enum values
  2. 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
  3. 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
  4. We use a special NullableEnumConverter to convert back to either null or a picked enum value for the ViewModel property setter
XML
<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:

C#
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);
    }
}

/// <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 EnumMemberAttribute, 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 =
                    (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false);

                return ((attributes.Length > 0) &&
                        (!String.IsNullOrEmpty(attributes[0].Value)))
                            ?
                                attributes[0].Value
                            : 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
}

public class NullHelper
{
    public static string NullComboStringValue
    {
        get
        {
            return "(None)";
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)