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, enum
s are obvious candidates for binding to items controls, such as list or combo boxes, in an application’s View.
Unfortunately, enum
s 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 string
s 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 enum
s or a lot of enum
s 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:
-->
<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:
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:
internal static class FontStylesHelper
{
#region Constructor
static FontStylesHelper()
{
FontStyleFriendlyNames = new Dictionary<FontStyles, string>
{
{FontStyles.Normal, "Normal Style"},
{FontStyles.Bold, "Bold Style"},
{FontStyles.Italic, "Italic Style"},
{FontStyles.BoldItalic, "Bold + Italic Style"},
};
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
:
public class FontStylesListProvider : IValueConverter
{
#region Implementation of IValueConverter
public object Convert
(object value, Type targetType, object parameter, CultureInfo culture)
{
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
:
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:
<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>
-->
<local:FontStylesListProvider x:Key="FontStylesListProvider"/>
-->
<local:FontStylesValueConverter x:Key="FontStylesValueConverter"/>
</Window.Resources>
<StackPanel Margin="0,100,0,0">
-->
<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 enum
s 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