Click here to Skip to main content
15,867,771 members
Articles / Desktop Programming / WPF

WPF Enum List Converter

Rate me:
Please Sign up or sign in to vote.
4.50/5 (6 votes)
12 Oct 2009Ms-PL4 min read 60.8K   1.4K   29   2
This article demonstrates how to bind a C# enum definition to a WPF combobox, which is not as simple as it first seems.

Introduction

This article demonstrates how to bind a C# enum definition to a WPF ComboBox, which is not as simple as it first seems. The solution was extracted from a real-world commercial application.

Background

Enum definitions are great for defining small fixed lists of options for a property. They are easy to define in code, and are strongly typed, so they help produce robust code. Unfortunately, these advantages become disadvantages when working with WPF, as enums are not suitable for XAML.

My data models tend to use a lot of enums to define class specific properties. I needed a way of putting the properties onto a form, and comboboxes or listboxes are the perfect controls for this. The problem is that the control's list of items needs to be defined and the value of the enum data property needs to be bound to the control's selected item.

I did not want to reference the combobox in the code-behind class, I wanted the bindings to be entirely defined on the XAML side.

Image 1

Using the Code

The solution to the enum problem is implemented in two small C# classes described below.

The source code contains everything needed to build the example, including the test data, which is taken from the SQL Server AdventureWorks sample database. The example code also makes use of some collapse converters, which are described in my previous article.

The example application allows the user to choose a product from a treeview and then edit its name and choose its color on the form on the right. The color enum is bound to three controls: ComboBox, ListBox, and TextBlock, in order to demonstrate how the same technique can be used across different controls.

C#
public class Product : TWGenericDataObject
{
    public enum eColor
    {
      None,
      Black,
      Light_Blue,
      Blue,
      DarkBlue,
      Grey,
      Multi,
      Red,
      Silver,
      White,
      Yellow
    }
    public eColor Color

This is the relevant part of the data model definition. We need to put those list of colors into the ComboBox and bind the Color property to the ComboBox's selected item. Note the Camel case and underscores that need to be removed.

The conversion is done by a custom converter object, and the first thing we need to do is to define it in the Windows Resources section in XAML.

XML
<Window.Resources>
  <local:ProductColorListConverter x:Key="productColorListConverter"/>
</Window.Resources>

Now we can use it inside our ComboBox definition:

XML
<ComboBox Grid.Column="1" Grid.Row="1" 
    Margin="2" Name="comboBoxColor" VerticalAlignment="Top"
    ItemsSource="{Binding Source={StaticResource productColorListConverter}}"
    SelectedIndex="{Binding ElementName=treeViewCategoriesAndProducts, 
                   Path=SelectedItem.Color, Mode=TwoWay, 
                   Converter={StaticResource productColorListConverter}}" />

Our converter is used in two different ways by the ComboBox. When bound to ItemsSource, it returns a collection of strings for each value in the enum definition. This fills the ComboBox's Items collection with human readable enum names.

When bound to SelectedIndex, it acts as a value converter to convert the enum property both to and from the combobox selection.

The ListBox implementation works in exactly the same way as they both share the same parent class: Selector.

That is all there is to do on the XAML side. We are not quite done yet, as there is one line of code that does need to be defined on the C# side.

C#
public class ProductColorListConverter : 
       TWWPFUtilityLib.TWEnumListConverter<TWSampleWPFDatabase.Product.eColor> { }

ProductColorListConverter is just a simple subclass of the generic class that does all the work. This has to be done in C# as XAML does not understand generic C# definitions.

How it Works

C#
public class TWEnumListConverter<TEnumType> : ObservableCollection<string>, IValueConverter
{
    public TWEnumListConverter()
    {
        HumanizeConverter hc = new HumanizeConverter();
        string[] names = Enum.GetNames(typeof(TEnumType));
        foreach (string s in names)
            Add(hc.Humanize(s));
    }

TWEnumListConverter is a generic class that takes the enum type as its template variable. The constructors converts the enum into a string array using the standard GetNames method. The enum names might be in Camel case or may contain underscores, so each name is passed through the HumanizeConverter (described below). The humanized names are added to itself, as the class inherits from ObservableCollection. It is this that allows the class to be used directly as a source of a ComboBox's list items.

C#
public object Convert(object value, Type targetType, 
       object parameter, System.Globalization.CultureInfo culture)
{
    int v = (int)value;
    return v;
}
public object ConvertBack(object value, Type targetType, 
       object parameter, System.Globalization.CultureInfo culture)
{
    int v = (int)value;
    Array values = Enum.GetValues(typeof(TEnumType));
    if ((v < 0) || (v >= values.Length))
        v = 0;
    TEnumType et = (TEnumType)values.GetValue(v);
    return et;
}

The other job of the class is to convert between the int type of the ComboBox's SelectedIndex and the property holding the enum value. Converting an int into an enum in a generic way takes more code than one might think. One of the benefits of this class is that it hides these details from the user.

As enum definitions cannot contain spaces, Camel case and the use of underscores are the alternatives that most people use. The job of the HumanizeConverter class is to put the spaces back into those names.

C#
public class HumanizeConverter : IValueConverter
{
    Regex reUnderscore = new Regex(@"_", 
          RegexOptions.Multiline | RegexOptions.CultureInvariant);
    Regex reCamel = new Regex(@"[a-z][A-Z]", 
                              RegexOptions.Multiline | 
                              RegexOptions.CultureInvariant);
    public static string SplitCamel(Match m)
    {
        string x = m.ToString();
        return x[0] + " " + x.Substring(1, x.Length - 1);
    }
    public string Humanize(object value)
    {
        string s = null;
        if (value != null)
        {
            s = value.ToString();
            s = reUnderscore.Replace(s, " ");
            s = reCamel.Replace(s, new MatchEvaluator(HumanizeConverter.SplitCamel));
        }
        return s;
    }
    public object Convert(object value, Type targetType, object parameter, 
                  System.Globalization.CultureInfo culture)
    {
        return Humanize(value);
    }
    public object ConvertBack(object value, Type targetType, object parameter, 
                  System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException("unexpected Convertback");
    }
}

It does the work using two Regular Expressions. As well as being used in the TWEnumListConverter, this class can also be used directly, for example, to bind an enum as the source of a TextBlock.

XML
<TextBlock Grid.Column="1" Grid.Row="4" Margin="2" 
  Name="textBlockColor" VerticalAlignment="Top" 
  Text="{Binding ElementName=treeViewCategoriesAndProducts, 
        Path=SelectedItem.Color, Converter={StaticResource humanizeConverter}}" />

Conclusion

Sometimes C# strong type checking can get in the way of a simple solution, which is maybe why dynamic languages, such as Ruby, are gaining in popularity. However, it is often the case, as hopefully this article shows, that a small utility class can make things easy to do, without losing the advantages of a strongly typed language.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer
United Kingdom United Kingdom
Tom has been developing software for 20 years and has done a lot of different stuff. Recently this has mostly been C# WPF/Silverlight, ASP.Net and Ruby On Rails.

Blog: http://rightondevelopment.blogspot.com/

Comments and Discussions

 
GeneralNo need for ObservableCollection Pin
Verbiest Kristof20-Oct-09 3:56
Verbiest Kristof20-Oct-09 3:56 
GeneralRe: No need for ObservableCollection Pin
Tom F Wright20-Oct-09 4:13
Tom F Wright20-Oct-09 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.