Thursday, November 4, 2010

Binding Enum values to WPF Item Collection Controls

There are several ways to bind an enumeration to an item collection control (like Listbox or Combobox) in WPF. Because we’re using XAML I will show you the declarative way. Let’s assume we have an enumeration that represents different ways of transmission:

public enum Transmission
{       
    None = 0,       
    Phone = 2,
    Mail = 4,
    Letter = 8,
    Chat = 16
}

This enumeration needs to be bound to a combo box in our WPF window. So the easiest way is to use an ObjectDataProvider which returns an array of the enum values. In XAML that would look like the following code snippet:

<ObjectDataProvider
    MethodName="GetValues"
    ObjectType="{x:Type System:Enum}"
    x:Key="TransmissionTypeEnum">
  <ObjectDataProvider.MethodParameters>
    <x:Type TypeName="App:Transmission"></x:Type>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

We define an ObjectDataProvider and tell it to use the GetValues method on the Enum object. Enum provides this static method which takes the type argument that represents the current enum type. So we add a method parameter and give it the type of our transmission enumeration. After defining the provider, you can use it right away as a binding source for an item collection control like so:

<ComboBox ItemsSource="{Binding Source={StaticResource TransmissionTypeEnum}}"

That’s it. Now, the combo box should display the values of the enum.
But what do we do, if we want to display user friendly text instead of the possibly technical enum definitions? Again, there are several ways to achieve this. The only thing you always need is a place to store the description for each enum field. Here we use the DescriptionAttribute class that is defined in the System.ComponentModel namespace. It allows us, to write the description right above each field:

public enum Transmission
{
    [DescriptionAttribute("None")]
    None = 0,

    [DescriptionAttribute("Telephone")]
    Phone = 2,

    [DescriptionAttribute("E-Mail Attachment")]
    Mail = 4,

    [DescriptionAttribute("Letter")]
    Letter = 8,

    [DescriptionAttribute("Chat (Text, Voice or Video)")]
    Chat = 16
}

Now we need a component to read out the descriptions and provide some kind of item source to bind to our combo box. Let’s create a static class that contains a method which gets the enum type and returns a Dictionary with the description for each field in the enumeration:

public static IDictionary<string, object> GetDict(Type enumType)
{
    var typeList = new Dictionary<string, object>();

    //get the description attribute of each enum field
    DescriptionAttribute descriptionAttribute;
    foreach (var value in Enum.GetValues(enumType))
    {
        FieldInfo fieldInfo = enumType.GetField((value.ToString()));
        descriptionAttribute =
(DescriptionAttribute)Attribute.GetCustomAttribute(fieldInfo,
typeof(DescriptionAttribute));
        if (descriptionAttribute != null)
        {
            typeList.Add(descriptionAttribute.Description, value);
        }
    }

    return typeList;
}

This code is really straight forward. It gets the description attribute for each field in the enumeration by using reflection. The description and the enum value itself are added to a dictionary which can be used as binding source for our combo box:

<ObjectDataProvider
    MethodName="GetDict"
    ObjectType="{x:Type App:EnumDescriptionValueDict}"
    x:Key="EnumDescriptionDict">
  <ObjectDataProvider.MethodParameters>
    <x:Type TypeName="App:Transmission"></x:Type>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ComboBox
      ItemsSource="{Binding Source={StaticResource EnumDescriptionDict}}"
      DisplayMemberPath="Key"
      SelectedValuePath="Value"/>

Because we are using a dictionary now, we have to tell the combo box the appropriate display and value path. Those are simply the properties of a single KeyValuePair within the dictionary. When you run the code, you will see the descriptions within the combo box, instead of the enumeration values.

Just one last thing for this post I want you to show is the binding of the selected value in the combo box to a view model property. Let’s assume we have a view model with a property of type Transmission. We want this property bound to the currently selected value of the combo box. All we need is a Converter that converts the enum value to string and also converts a string to an enum value. Therefore I’ll name this converter EnumToStringConverter:

[ValueConversion(typeof(Enum), typeof(String))]
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value.ToString();
    }

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value == null ? null : Enum.Parse(targetType,
                                         value.ToString(), true);
    }
}

With the value converter defined and the according property on the view model, we can now add the following line to our combo box:

SelectedValue="{Binding Transmission, Converter={StaticResource EnumConverter}, Mode=TwoWay}"

Here you go. With an enum bound to a combo box that shows custom descriptions and promotes its selected value right to the underlying view model. Enjoy it!

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello. If you try the second option, the one with the binding to a view model, it throws an exception, because the ConvertBack method takes as parameter the whole key/value from the dictionary.

    In order to fix it, add to the combobox the "SelectedValuePath="Value"" option.
    So the final Combobox definition in WPF should be:

    ComboBox
    ItemsSource="{Binding Source={StaticResource EnumDescriptionDict}}"
    DisplayMemberPath="Key"
    SelectedValuePath="Value"
    SelectedValue="{Binding Transmission, Converter={StaticResource EnumConverter}, Mode=TwoWay}"/

    ReplyDelete
  3. What is EnumDescriptionValueDict?

    ReplyDelete