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

Data Binding Enums in WPF

Rate me:
Please Sign up or sign in to vote.
4.84/5 (8 votes)
23 Aug 2011CPOL9 min read 44.3K   689   24   4
Binding enums to UI elements in WPF using friendly names
Screen_Shot.png

Introduction

.NET provides the enum construct to create a set of constants, such as the days of the week, or the months of a year, in a single object. I find them very handy in a wide variety of situations, particularly those involving pick-lists, where one value is picked from a list of options. As such, enums are obvious candidates for binding to items controls, such as list or combo boxes, in an application’s View.

Unfortunately, enums do not readily lend themselves to user-friendly display in the View. The problem is that enum members cannot contain spaces, which can lead to usability issues when binding an enum to a WPF items control—users do not like seeing “CustomerWith10PctDisc” in combo boxes. This article presents a simple solution to that problem.

There are several different approaches to providing user-friendly counterparts to enum members for display in the View. Sacha Barber has a great article on the subject on CodeProject, and I recommend looking it over, particularly if you need to localize the user-friendly strings that will be displayed in the UI. Sacha’s article uses reflection to read localizable description attributes that adorn each member of the enum, which works very well, unless one has very long enums or a lot of enums to be displayed on the string. In those cases, reflection can slow down the combo box, resulting in a noticeable lag before the drop-down list appears in a combo box.

In the application I am developing, I don’t need localization, and as I read Sacha’s article, I wondered if I could get the job done without resorting to reflection. The idea of using a dictionary came to mind immediately, and the result is the demo application attached to this article. I was surprised at the simplicity of the solution, compared to some of the others I had come across. I think Sacha’s approach is a bit more elegant, but the dictionary approach described below is simple, and it gets the job done.

I am interested in feedback and suggestions as to this particular approach. One of the benefits of publishing on CodeProject is the quality of the peer review that is provided. I will update the article periodically to reflect feedback and suggestions, with credit to the originator, of course.

The Demo App

The demo app is a very simple app structured around the MVVM pattern. The view model has one command property and one data property. The command property is implemented as a separate ICommand object, rather than as inline code in the view model.

The main window shows a combo box, which is bound to the view model:

XML
<!-- Note that the ItemsSource binding is a 'fake' binding. The items
are generated by the value converter referenced in the binding. -->

<ComboBox ItemsSource="{Binding Converter={StaticResource FontStylesListProvider}}"
          SelectedItem="{Binding Path=FontStyle, Converter={StaticResource
                         FontStylesValueConverter}, Mode=TwoWay,
                         UpdateSourceTrigger=PropertyChanged}"
Width="200" Height ="26" />

The ItemsSource property is bound to the view model generally—it is not bound to any particular property. That’s because it does not actually import its list of values from the view model. Instead, the list is generated by the FontStylesListProvider referenced in the binding.

I spent some time debating whether to use this approach. Quite frankly, it has a bit of a code smell to me. Value converters should convert values, not generate them. But this approach simplified things rather significantly, so after a bit of angst and a couple of beers, I decided to go with it. A comment in the XAML points out that the data binding is ‘faked’, and I would strongly recommend adding such a comment to anyone else implementing this approach.

The SelectedItem property of the combo box is bound to the FontStyle property in the view model. It uses a second value converter, FontStylesValueConverter, to convert between enum members and user-friendly counterparts. The conversion is two-way; it will convert enum values to friendly names, and vice-versa. I will discuss both converters at more length below.

In addition to the combo box, the main window has two text blocks that show the raw, unconverted enum value currently held in the view model’s FontStyle property. These text blocks provide verification that the enum-to-friendly string conversion is performed correctly in both directions. Finally, the main window contains a button that sets the view model property to ‘BoldItalic’. This button can be clicked to verify that the view updates properly when the view model’s FontStyle property value is changed by code, rather than by a combo-box selection.

The View Model

The view model contains a single data property, FontStyle. The property is typed to FontStyles, the enum that we will bind to the combo box in the view. In addition, the view model contains a command property, SetFontStyle, which is instantiated by the view model constructor to a custom ICommand object, SetFontStyleCommand. The property-setting code is found in the ICommand’s Execute() method.

The Enum

The enum in the demo class is a very simple list of font styles:

C#
public enum FontStyles { Normal, Bold, Italic, BoldItalic }

The first three elements of the enum would look fine in a combo box, but the fourth member would look pretty ugly. A user-friendly name, such as “Bold + Italic” would be much better. The helper class and converters provide friendly names for all four elements of the enum.

The Helper Class

The conversion between enum elements and friendly names is built around a dictionary object. The FontStylesHelper class creates and maintains this dictionary. Actually, there are two dictionaries; one for forward (enum-to-friendly) conversions, and a mirror dictionary for reverse (friendly-to enum) conversions. The two converter classes call the helper class to perform conversion lookups. For convenience, all three classes can be bundled with the enum in a single code file. In the demo app, the code file is named FontStyles.cs.

The helper class is an internal static class—it is not intended for access by anything other than the converters in the code file. It has a constructor and two properties:

C#
internal static class FontStylesHelper
{
#region Constructor

static FontStylesHelper()
{
// Create enum-to friendly name dictionary
FontStyleFriendlyNames = new Dictionary<FontStyles, string>
{
{FontStyles.Normal, "Normal Style"},
{FontStyles.Bold, "Bold Style"},
{FontStyles.Italic, "Italic Style"},
{FontStyles.BoldItalic, "Bold + Italic Style"},
};

// Create friendly name-to-enum dictionary
FontStyleEnumValues = FontStyleFriendlyNames.ToDictionary(x => x.Value, x => x.Key);
}

#endregion

#region Properties

public static Dictionary<FontStyles, String> FontStyleFriendlyNames { get; set; }

public static Dictionary<String, FontStyles> FontStyleEnumValues { get; set; }

#endregion
}

The constructor creates a Dictionary<FontStyles, String> object, which converts an enum member into its user-friendly counterpart. Then it creates a reverse-lookup dictionary, to convert from a user-friendly name back to the corresponding enum member.

Finally, the helper class contains two properties, one for each dictionary. The converters access these properties to perform their conversions.

The Font Styles List Provider

The first converter is the FontStylesListProvider:

C#
public class FontStylesListProvider : IValueConverter
{
#region Implementation of IValueConverter

public object Convert
	(object value, Type targetType, object parameter, CultureInfo culture)
{
/* Note that this converter does not convert a value passed in. Instead, it generates
* a list of user-friendly counterparts for each member of the target enum and
* returns that list to the caller. */

var fontStyleList = FontStylesHelper.FontStyleFriendlyNames.Values.ToList();
return fontStyleList;
}

public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}

#endregion
}

This converter doesn’t really convert, which is why it is named as a ‘provider’, and not as a ‘converter’. It queries the friendly names dictionary for its list of values and returns that list to the caller. This converter is called when the ItemsSource property of the combo box is resolved.

Note that we only need a forward ‘conversion’ (in the direction of the view) of the enum—the only job this converter needs to perform is to provide the list of friendly names that will be displayed in the drop-down portion of the combo box. It does not need to carry anything back to the view model. Since we do not need a reverse conversion (in the direction of the view model), we leave the ConvertBack() method unimplemented.

The Font Styles Value Converter

The second converter is the FontStylesValueConverter:

C#
public class FontStylesValueConverter: IValueConverter
{
#region Implementation of IValueConverter

public object Convert
	(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumValue = (FontStyles)value;
var friendlyName = FontStylesHelper.FontStyleFriendlyNames[enumValue];
return friendlyName;
}

public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
var friendlyName = (String)value;
var enumValue = FontStylesHelper.FontStyleEnumValues[friendlyName];
return enumValue;
}

#endregion
}

This converter provides full, two-way conversion between enum members and their user-friendly counterparts. Enum values passed from the view model to the view are converted to friendly names, and friendly names that are passed from the view to the view model are converted back to enum values.

Unlike the first converter, this converter is used conventionally. The code is quite simple—in each case, the value passed in is cast to the appropriate type; then its counterpart is looked up in the appropriate dictionary, and the result is returned.

Binding the Enum to the Combo Box

The demo app performs all data binding in XAML. The binding markup is very simple—unlike other solutions, we don’t need an ObjectDataProvider, and we don’t need a DataTemplate for the combo box:

XML
<Window x:Class="WpfEnumBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfEnumBindingDemo" Title="WPF Enum Binding Demo"
Height="350" Width="525">

    <Window.Resources>

        <!-- Value Converter to provide friendly name list for FontStyles enum -->
        <local:FontStylesListProvider x:Key="FontStylesListProvider"/>

        <!-- Value Converter to convert between 
		FontStyles enum and user-friendly names -->
        <local:FontStylesValueConverter x:Key="FontStylesValueConverter"/>

    </Window.Resources>

    <StackPanel Margin="0,100,0,0">

        <!-- Note that the ItemsSource binding is a 'fake' binding. The items
        are generated by the value converter referenced in the binding. -->

        <ComboBox ItemsSource="{Binding Converter=
			{StaticResource FontStylesListConverter}}"
                  SelectedItem="{Binding Path=FontStyle,
                                 Converter={StaticResource FontStylesValueConverter},
                                 Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  Width="200" Height ="26" />

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Enum value in view model:" 
			FontStyle="Italic" Margin="155,10,0,0" />
            <TextBlock Text="{Binding Path=FontStyle, Mode=TwoWay,
                              UpdateSourceTrigger=PropertyChanged}"
                       Width="50" Height ="26" Margin="5,10,0,0" />
        </StackPanel>
    </StackPanel>
</Window>

Note that we provide a local namespace declaration, and we declare our value converters as resources of the window in the usual manner. We bind two combo box properties:

  • The ComboBox.ItemsSource property is bound to the first converter, which provides the list of user-friendly names that populate the drop-down list.
  • We bind the ComboBox.SelectedItem property to the second converter, to perform the two-way conversion between enum values and user-friendly names.

The two text blocks below the combo box show the raw, unconverted enum value held in the view model’s FontStyle property. The first text block acts as a label, and the second displays the enum value. They serve to verify that the conversion is being performed in both directions as expected.

Using the Code

It is simple to implement the approach to enum binding described here. Here are the steps:

Step 1: Copy the code file FontStyle.cs to your application, changing its name to the name of the enum that you want to bind.

Step 2: Replace the FontStyle enum in the code file with the enum you want to bind.

Step 3: Change the key type in the Dictionary<TKey, TValue> declaration to the name of your enum. For example, Dictionary<FontStyles, String> would become Dictionary<MyEnum, String>.

Step 4: Replace the list of enum values and corresponding value names (the key-value list) in the FontStylesHelper constructor with a list appropriate for your enum.

The code file could be further enhanced by using reflection in the helper class constructor to read description attributes from the enum and build the dictionary list. I opted not to use that approach, because I didn’t want the overhead associated with reflection.

Conclusion

I really like the simplicity of this approach. The value converters are all just a couple of lines long, which I view as a characteristic of a good converter. Both of the converters are driven by the same dictionaries, and both of the dictionaries are generated from the same list of keys and values. So, there is really no code duplication.

As I mentioned above, I’m not so crazy about the idea of using a converter to generate a list of values, as opposed to converting from one value to another. I am open to other approaches that achieve the same result in a more conventional manner, and I welcome your comments.

Hopefully, the solution presented in this article will simplify the process of binding enums to item controls in your WPF applications. If you have any questions about the general approach, please post them below.

History

  • 2011/08/23: Initial version completed

License

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


Written By
Software Developer (Senior) Foresight Systems
United States United States
David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Rick Dean16-Dec-11 5:55
Rick Dean16-Dec-11 5:55 

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.