Binding To Nullable Enums

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

Advertisements

3 thoughts on “Binding To Nullable Enums

  1. Rather than using ObjectDataProvider and CompositeCollection, you could use a markup extension:

    It makes for much cleaner XAML code…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: