Click here to Skip to main content
Click here to Skip to main content

Using DescriptionAttribute for enumerations bound to a ComboBox

, 16 Nov 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
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 converter is as follows:

class EnumDescriptionConverter : IValueConverter
{
  private class LocalDictionaries
  {
    public readonly Dictionary<int, string> EnumDescriptions = 
        new Dictionary<int, string>();
    public readonly Dictionary<string, int> EnumIntValues = 
        new Dictionary<string, int>();
  }
 
  private readonly Dictionary<Type, LocalDictionaries> _localDictionaries =
    new Dictionary<Type, LocalDictionaries>();
 
  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());
    return _localDictionaries[value.GetType()].EnumDescriptions[(int)value];
  }
 
  public object ConvertBack(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    if (value == null || !targetType.IsEnum)
      return value;
    if (!_localDictionaries.ContainsKey(targetType))
      CreateDictionaries(targetType);
    int enumInt = _localDictionaries[targetType].EnumIntValues[value.ToString()];
    return Enum.ToObject(targetType, enumInt);
  }
 
  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.EnumDescriptions.Add((int)value, 
          valueDescription[0].Description);
        dictionaries.EnumIntValues.Add(valueDescription[0].Description, 
          (int)value);
      }
      else //Use the value for display if not concrete result
      {
        dictionaries.EnumDescriptions.Add((int)value, value.ToString());
        dictionaries.EnumIntValues.Add(value.ToString(), (int)value);
      }
    }
    _localDictionaries.Add(e, dictionaries);
  }
}

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.

Note that a dictionary is used to save the two translation dictionaries. 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.

A separate, but much simpler converter is used for the ItemSource converter. It is simpler because there is no need to code the ConvertBack method, and only a list of descriptions needs to be created:

class EnumDescriptionSourceConverter : IValueConverter
{
  private readonly Dictionary<Type, List<string>> _localLists =
    new Dictionary<Type, List<string>>();
 
  public object Convert(object value, Type targetType, object parameter,
    System.Globalization.CultureInfo culture)
  {
    if (value == null || !value.GetType().IsEnum)
      return value;
    if (!_localLists.ContainsKey(value.GetType()))
      CreateList(value.GetType());
    return _localLists[value.GetType()];
  }
 
  public object ConvertBack(object value, Type targetType, object parameter,
    System.Globalization.CultureInfo culture)
  {
    throw new NotImplementedException("There is no backward conversion");
  }
 
  private void CreateList(Type e)
  {
    var list = new List<string>();
 
    foreach (var value in Enum.GetValues(e))
    {
      FieldInfo info = value.GetType().GetField(value.ToString());
      var valueDescription = (DescriptionAttribute[])info.GetCustomAttributes
            (typeof(DescriptionAttribute), false);
      list.Add(valueDescription.Length == 1 ?
              valueDescription[0].Description : value.ToString());
    }
    _localLists.Add(e, list);
  }
}

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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:EnumComboBoxBindingWithDescExample"
        Title="Enum Description ComboBox Example" Height="200" Width="300">
  <Window.Resources>
    <local:EnumDescriptionConverter x:Key="EnumDescriptionConverter"/>
    <local:EnumDescriptionSourceConverter x:Key="EnumDescriptionSourceConverter"/>
  </Window.Resources>
    <Grid>
    <ComboBox Height="22" Width="200"
      SelectedItem="{Binding SampleEnum,
        Converter={StaticResource EnumDescriptionConverter}}"
      ItemsSource="{Binding SampleEnum,
        Converter={StaticResource EnumDescriptionSourceConverter}}"/>
    </Grid>
</Window>

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 two value converters 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.

License

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

Share

About the Author

Clifford Nelson
Software Developer (Senior) ETeam/Delloitte
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last three years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with NBC Universal in Universal City, CA

Comments and Discussions

 
QuestionGuess there are more elegant ways! Pinmembervallarasus20-Nov-11 8:50 
QuestionThoughts PinmemberPIEBALDconsult16-Nov-11 8:50 
AnswerRe: Thoughts PinmemberClifford Nelson16-Nov-11 10:02 
QuestionSimilar PinmemberCollin Jasnoch11-Nov-11 11:18 
AnswerRe: Similar PinmemberClifford Nelson14-Nov-11 10:37 
I am really like using extension methods, and have used them extensively in some applications I have written. If I had wanted to get the DisplayAttribute for use in code, I would have used the same method that you did of creating an extension method. There may be a problem with using the EnumValueOf<T> within the converter because the complier complains about EnumValueOf<targetType>. Could use it if could use generics for the converter, but I do not know if you can. Works great within normal code though.
GeneralRe: Similar PinmemberCollin Jasnoch14-Nov-11 11:05 
GeneralRe: Similar PinmemberClifford Nelson14-Nov-11 13:20 
SuggestionStrange Way PinmemberReto Ravasio10-Nov-11 15:14 
GeneralRe: Strange Way PinmemberClifford Nelson11-Nov-11 10:59 
AnswerRe: Strange Way PinmemberReto Ravasio11-Nov-11 16:54 
GeneralRe: Strange Way PinmemberClifford Nelson14-Nov-11 10:23 
GeneralRe: Strange Way PinmemberReto Ravasio15-Nov-11 7:30 
GeneralRe: Strange Way PinmemberClifford Nelson20-Nov-11 15:31 
GeneralNice and to the point article PinmemberRabinDl10-Nov-11 10:18 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411022.1 | Last Updated 16 Nov 2011
Article Copyright 2011 by Clifford Nelson
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid