Scott Dorman

ephemeral segment

  Home  |   Contact  |   Syndication    |   Login
  603 Posts | 10 Stories | 862 Comments | 51 Trackbacks

News


Post Categories

Image Galleries


Microsoft Store


Creative Commons License



Locations of visitors to this page

Subscribers to this feed

TwitterCounter for @sdorman

View blog authority

Add to Technorati Favorites

Windows Live Alerts

AddThis Social Bookmark Button

LinkedIn profile

Community Credit profile

The Code Project

Follow me on Twitter

Get Free Shots from Snap.com

Community Credit Hall of Fame

Get Feedghost

Xobni outlook add-in for your inbox



Support This Site

Tag Cloud


Article Categories

Archives

Post Categories

Image Galleries

Every once in a while I need to bind an enumerated type to a Windows Forms control, usually a ComboBox.

The simplest is to use the Enum.GetValues() method, setting its result to the DataSource property of the ComboBox. If you have the following enum:

   1: public enum SimpleEnum
   2: {
   3:    Today,
   4:    Last7
   5:    Last14,
   6:    Last30,
   7:    All
   8: }

You can bind it to a ComboBox like this:

   1: ComboBox combo = new ComboBox();
   2: combo.DataSource = Enum.GetValues(typeof(SimpleEnum));

While this does work, there are a couple of problems with this:

  1. There is no support for localization.
  2. There is no support for more readable descriptions.

If you want to support one or both of these requirements, it can start to get complicated. The complication comes in the form of numerous different ways to accomplish the same thing. These range from custom type descriptors, to generic (and non-generic) "details" classes, to hard-coded string resource lookups, and many others.

The majority of the articles and techniques I have seen all attempt to solve the simple problem of data binding an enum to a ComboBox while displaying a human readable string as the ComboBox display string. Overall, there isn't anything wrong with these approaches and they all have pros and cons. However, they are generally more complicated than necessary and, in some cases, require a lot of work on either the developer implementing the enum, the developer using it, or both.

The easiest way to accomplish this is actually using a little bit of Reflection and decorating the enum values with an attribute. You don't need generics, custom classes, or custom type descriptors...just two static methods that are both less than 10 lines of code.

The first step is to add a description attribute to your enum. For simplicity, you can use the System.ComponentModel.DescriptionAttribute class, but I would recommend deriving your own EnumDescriptionAttribute.

   1: /// <summary>
   2: /// Provides a description for an enumerated type.
   3: /// </summary>
   4: [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false)]
   5: public sealed class EnumDescriptionAttribute :  Attribute
   6: {
   7:     private string description;
   8:  
   9:     /// <summary>
  10:     /// Gets the description stored in this attribute.
  11:     /// </summary>
  12:     /// <value>The description stored in the attribute.</value>
  13:     public string Description
  14:     {
  15:         get
  16:         {
  17:             return this.description;
  18:         }
  19:     }
  20:  
  21:     /// <summary>
  22:     /// Initializes a new instance of the 
  23:     /// <see cref="EnumDescriptionAttribute"/> class.
  24:     /// </summary>
  25:     /// <param name="description">The description to store in this attribute.</param>
  26:     public EnumDescriptionAttribute(string description)
  27:         : base()
  28:     {
  29:         this.description = description;
  30:     }
  31: }

Now that we have our description attribute, we need to decorate the enum with it:

   1: public enum SimpleEnum
   2: {
   3:    [EnumDescription("Today")]
   4:    Today,
   5:  
   6:    [EnumDescription("Last 7 days")]
   7:    Last7,
   8:  
   9:    [EnumDescription("Last 14 days")]
  10:    Last14,
  11:  
  12:    [EnumDescription("Last 30 days")]
  13:    Last30,
  14:  
  15:    [EnumDescription("All")]
  16:    All
  17: }

Using a custom attribute allows you to keep the human readable description in the code where the enum is defined. It also allows you to retrieve localized versions of the description. In order to do that, you simply need to change the way the attribute works to lookup the appropriate string in the string resources.

The next part is what actually does all of the work. As I mentioned, both of these functions are less than 10 lines of code. The easiest way to work with these functions is to create a static (or sealed, if you aren't using .NET 2.0 or later) class that holds these two static functions.

   1: /// <summary>
   2: /// Provides a static utility object of methods and properties to interact with enumerated types. 
   3: /// </summary>
   4: public static class EnumHelper
   5: {
   6:    /// <summary>
   7:    /// Gets the <see cref="DescriptionAttribute"/> of an <see cref="Enum"/> type value.
   8:    /// </summary>
   9:    /// <param name="value">The <see cref="Enum"/> type value.</param>
  10:    /// <returns>A string containing the text of the <see cref="DescriptionAttribute"/>.</returns>
  11:    public static string GetDescription(Enum value)
  12:    {
  13:       if (value == null)
  14:       {
  15:          throw new ArgumentNullException("value");
  16:       }
  17:  
  18:       string description = value.ToString();
  19:       FieldInfo fieldInfo = value.GetType().GetField(description);
  20:       EnumDescriptionAttribute[] attributes = (EnumDescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
  21:  
  22:       if (attributes != null && attributes.Length > 0)
  23:       {
  24:          description = attributes[0].Description;
  25:       }
  26:       return description;
  27:    }
  28:  
  29:    /// <summary>
  30:    /// Converts the <see cref="Enum"/> type to an <see cref="IList"/> compatible object.
  31:    /// </summary>
  32:    /// <param name="type">The <see cref="Enum"/> type.</param>
  33:    /// <returns>An <see cref="IList"/> containing the enumerated type value and description.</returns>
  34:    public static IList ToList(Type type)
  35:    {
  36:       if (type == null)
  37:       {
  38:          throw new ArgumentNullException("type");
  39:       }
  40:  
  41:       ArrayList list = new ArrayList();
  42:       Array enumValues = Enum.GetValues(type);
  43:  
  44:       foreach (Enum value in enumValues)
  45:       {
  46:          list.Add(new KeyValuePair<Enum, string>(value, GetDescription(value)));
  47:       }
  48:  
  49:       return list;
  50:    } 
  51: }

As you can see, the GetDescription method uses a little bit of Reflection to retrieve the EnumDescription attribute on the specified enum value. If it doesn't find the attribute, it simply uses the value name. The ToList method returns an IList of KeyValuePair<Enum, string> instances that hold the enum value (the key) and the description (the value). If you aren't using .NET 2.0 or later, you will need to use DictionaryEntry instead of KeyValuePair<TKey, TValue>.

In order to bind the ComboBox, you now need to do the following:

   1: ComboBox combo = new ComboBox();
   2: combo.DataSource = EnumHelper.ToList(typeof(SimpleEnum));
   3: combo.DisplayMember = "Value";
   4: combo.ValueMember = "Key";

You now have a ComboBox whose values are your enum type values and whose display are the string specified in the EnumDescription attribute. This works with any control that supports data binding, including the ToolStripComboBox, although you will need to cast the ToolStripComboBox.Control property to a ComboBox to get to the DataSource property. (In that case, you will also want to perform the same cast when  you are referencing the selected value to work with it as your enum type.)

posted on Thursday, August 02, 2007 12:21 AM

Feedback

# re: Data Binding an Enum with Descriptions 4/23/2012 4:39 AM Berend Otten
Great post! But how can I read extra added attributes?
Example: Instead of using the enum-element name, enum element description I want to add another integer value tot the enum. I used the example class, but can't see how to retrieve the value upon selecting an element in the combobox. Please, can you show how?

/// <summary>
/// Provides a integer value for an enumerated type.
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false)]
public sealed class EnumIntegerAttribute : Attribute
{
private int intValue;

public string IntValue
{
get
{
return this.intValue;
}
}

public EnumIntegerAttribute(int _intValue)
: base()
{
this.intValue = _intValue;
}
}



Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: