Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using DescriptionAttribute for enumerations bound to a WPF ComboBox

0.00/5 (No votes)
27 Jan 2016 1  
How to use DescriptionAttribute for enumerations bound to a ComboBox.

Introduction

A great maintenance headache is maintaining the View with changes in enumerations, and also just making sure that the enumeration is associated with the right item in a ComboBox (possibly a ListBox).

Background

I have thought about how to deal with this problem for a while. I was considering ways of using the name for each item in the enumeration as the associated name in the UI. If I did this, I could use the enumeration type as the ItemSource for the ComboBox, and then translate the string back to the enumeration. The problem is that sometimes you want to have spaces in the View to the user (besides the issue that the name for the enumeration states may not be what should be displayed). I thought of two possibilities, using underlines in the enumeration names and translating them to spaces using a value converter, or using a value converter to insert a space in front of each capital letter. Then I found out that I can associate a description to each enumeration value using the DescriptionAttribute. It is then possible to get to this description string, and then it is possible get an enumeration of descriptions to use as an item source, and to convert an enumeration value to its description and convert a description to the associated enumeration.

Implementation

The example that I have created for this paper is very simple. It consists of a ComboBox that has the enumeration descriptions as the ComboItems.

The first thing that is needed is the enumeration with a description for each enumeration. I created something very basic for this example:

public enum SampleEnum
 {
     [DescriptionAttribute("I like the color blue")]
     Blue,
     [DescriptionAttribute("I like the color green")]
     Green,
     [DescriptionAttribute("I like the color yellow")]
     Yellow,
     Orange,
     [DescriptionAttribute("I like the color red")]
     Red
 }

One of the enumerations does not have a DescriptionAttribute so I can show that the code handles this situation.

Now that we have the enumeration, we need two value converters, one to convert an enumeration value to a description and another for description to an enumeration. There is no need to have a different value converter for each enumeration since the enumeration Type is available to the value converter. The implementation of this dictionary creation is as follows:

private class LocalDictionaries
{
 public readonly Dictionary<int, string> Descriptions = new Dictionary<int, string>();
 public readonly Dictionary<string, int> IntValues = new Dictionary<string, int>();
 public IEnumerable<string> ItemsSource;
}

private readonly Dictionary<Type, LocalDictionaries> _localDictionaries =
 new Dictionary<Type, LocalDictionaries>();

private void CreateDictionaries(Type e)
{
 var dictionaries = new LocalDictionaries();

 foreach (var value in Enum.GetValues(e))
 {
  FieldInfo info = value.GetType().GetField(value.ToString());
  var valueDescription = (DescriptionAttribute[])info.GetCustomAttributes
   (typeof(DescriptionAttribute), false);
  if (valueDescription.Length == 1)
  {
   dictionaries.Descriptions.Add((int)value, valueDescription[0].Description);
   dictionaries.IntValues.Add(valueDescription[0].Description, (int)value);
  }
  else //Use the value for display if not concrete result
  {
   dictionaries.Descriptions.Add((int)value, value.ToString());
   dictionaries.IntValues.Add(value.ToString(), (int)value);
  }
 }
 dictionaries.ItemsSource = dictionaries.Descriptions.Select(i => i.Value);
 _localDictionaries.Add(e, dictionaries);
}

Each Dictionary entry has a key that is the Type value for the enumeration. The Value portion is an object that contains two translation dictionaries and the IEnumerable used for the ItemsSource. The key for this dictionary is the type. This is because the same instance of the converter is used for all conversions. This would not be a problem with the example, but if I had a second enumeration ComboBox using a different enumeration, there would be a problem. It would be an option to go through all the enumeration values each time, but I believe that that would have a performance impact, so it makes more sense to maintain the information for all the enumeration types, paying the small penalty for the lookup. Also note that a class is used for the dictionary pair.

Two more dictionaries that are used to lookup the string associated with the enumeration using the a key that is the enumeration's integer values, and to look up the enumeration's integer value using the string associated with the enumeration. The string associated with the enumeration will be the DescriptionAttribute if the one is defined for the enumeration, or the ToString() value is one is not. This allows quick conversion at the expense of memory.

When converting the enumeration to the description, there is first a check that the type is an enumeration since there is no point in continuing otherwise (for the Convert method, the type of the value argument is checked, and for ConvertBack, the targetType argument is checked). Then there is a check that the two dictionaries needed to convert to/from a description string have been initialized, and if not, calls the method to create the two dictionaries.

It is this CreateDictionaries method where the magic happens. Here, the FieldInfo for each enumeration is interrogated for a DescriptionAttribute. If this attribute exists, then the value is associated with the enumeration in both dictionaries, otherwise the ToString() value of the enumeration is used. With the two dictionaries created, it is now possible to look up the description using the integer value of the enumeration, or look up the integer value of the enumeration using the description, making it quick to convert between the two for the Convert and ConvertBack methods. Probably unnecessary to do the check for the dictionaries to be initialize in the method, but the converter may be used in some unusual way, so I do not consider it back to add a simple check. Now everything is in place to return the description or enumeration value as there is no issue with converting the enumeration either from (for the ConvertBack once the dictionary lookup has been done) or to (for the Convert, the value argument being converted before using the dictionary) an Integer. Using an integer for the dictionaries instead of the enumeration lets the dictionaries work without the converter being customized to a specific enumeration type.

The converter section:

public object Convert(object value, Type targetType,
 object parameter, System.Globalization.CultureInfo culture)
{
 if (value == null || !value.GetType().IsEnum)
  return value;
 if (!_localDictionaries.ContainsKey(value.GetType()))
  CreateDictionaries(value.GetType());
 //This is for the ItemsSource
 if (targetType == typeof(IEnumerable))
  return _localDictionaries[value.GetType()].ItemsSource;

 //Normal SelectedItem case where it exists
 if (_localDictionaries[value.GetType()].Descriptions.ContainsKey((int)value))
  return _localDictionaries[value.GetType()].Descriptions[(int)value];

 //Have to handle 0 case, else an issue
 if ((int)value == 0) return null;

 //Error condition
 throw new InvalidEnumArgumentException();
}

public object ConvertBack(object value, Type targetType,
 object parameter, System.Globalization.CultureInfo culture)
{
 if (value == null || (!targetType.IsEnum && !targetType.IsGenericType)) return value;
 if (targetType.IsGenericType) targetType = Nullable.GetUnderlyingType(targetType);
 if (!_localDictionaries.ContainsKey(targetType))
  CreateDictionaries(targetType);
 int enumInt = _localDictionaries[targetType].IntValues[value.ToString()];
 return Enum.ToObject(targetType, enumInt);
}

Note: it is possible to get by with one value converter by checking the target type, and return a collection of descriptions if the type is IEnumerable.

With the converters and the enumeration, it is very straightforward to create the XAML to create a more maintainable ComboBox for enumerations:

<Window x:Class="EnumComboBoxBindingWithDescExample.MainWindow"
    xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
    xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
    xmlns:local="clr-namespace:EnumComboBoxBindingWithDescExample"
    Title="Enum Description ComboBox Example" Height="200" Width="300">
 <Window.Background>
  <SolidColorBrush Color="{Binding Text,ElementName=SelectedColor}"/>
 </Window.Background>

Note: the figure is not the same XAML as above. It is similar, but adds some decoration, including a TextBox to show the actual enumeration value of the item selected in the ComboBox, and that TextBox is bound to the Background of the window (a nice feature is that directly binding to the ViewModel value will not work because only after it is translated into text will it actually specify a color).

Conclusion

With simply one converter the detects if target is IEnumerable, and the use of the DescriptionAttribute when defining an enumeration, it is possible create a ComboBox for selecting options defined by the enumeration, with the DescriptionAttribute argument specifying the text for the ComboBoxItems; no need to specify ComboBoxItems in the XAML. This enhances maintainability, removing the need to coordinate changes in the enumeration with the UI, and allows defining the enumerations and the text associated with the enumerations in one place. There is also the advantage that if an enumeration is used with a ComboBox in more than one place, we still have the ComboBoxItem.Text in one place without having to add to the ViewModel, or add a static variable somewhere, and the associated text is with the enumeration instead of being hidden in a ViewModel or a static variable.

The one drawback to this concept that I know of is internationalization. There is also the issue that when space is available, it is preferred to use radio buttons to a ComboBox since it is quicker for the user with less steps and the options are readily obvious. We can still use value converters, but a little more work is required to setup a ListBox to display RadioButtons.

Updates

November 14, 2011: Made a mistake in thinking that could not have a dictionary of List when Visual Studio complained about a second “>” in “Dictionary<Type, List<string>>”. This error was pointed out by Reto Ravasio and I wish to thank him for the correction. I have changed the code for the converter used to return an enumeration of descriptions to use a dictionary of Lists and fixed the article appropriately.

September 30 2015: Updated code to handle Nullable enumerations, and fixed some issues with the layout.

January 27, 2016: Created new version that only has one converter, using the same converter for binding both the ItemsSource and the SelectedItem. Thus when binding, the same converter is used:

<ComboBox ItemsSource="{Binding LensType,
                                Converter={converters:EnumBindingConverter}}"
          SelectedItem="{Binding LensType,
                                 Converter={converters:EnumBindingConverter},
                                 UpdateSourceTrigger=PropertyChanged}" />

March 10 2016: Fixed bug in EnumBindingConverter.

June 24 2006: Fixed ConvertBack so that handles Nullable enumerations and updated a lot of the description to match the changes in the code in the sample.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here