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

Data Binding an Enum with Descriptions

By , 30 Dec 2007
 

Introduction

Every once in a while I need to bind an enumerated type to a Windows Forms control, usually a ComboBox. There are lots of articles here on The CodeProject that present various ways to do this, each with their own 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 Simple Way

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:

public enum SimpleEnum
{
    Today,
    Last7
    Last14,
    Last30,
    All
}

You can bind it to a ComboBox like this:

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

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

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

Binding with Descriptions

Fortunately, there is a relatively easy way to meet these requirements using a little bit of Reflection and decorating the enum values with an attribute. You don't need a lot of generic classes, 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.

/// <span class="code-SummaryComment"><summary></span>
/// Provides a description for an enumerated type.
/// <span class="code-SummaryComment"></summary></span>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, 
 AllowMultiple = false)]
public sealed class EnumDescriptionAttribute :  Attribute
{
   private string description;

   /// <span class="code-SummaryComment"><summary></span>
   /// Gets the description stored in this attribute.
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><value>The description stored in the attribute.</value></span>
   public string Description
   {
      get
      {
         return this.description;
      }
   }

   /// <span class="code-SummaryComment"><summary></span>
   /// Initializes a new instance of the
   /// <span class="code-SummaryComment"><see cref="EnumDescriptionAttribute"/> class.</span>
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><param name="description">The description to store in this attribute.</span>
   /// <span class="code-SummaryComment"></param></span>
   public EnumDescriptionAttribute(string description)
       : base()
   {
       this.description = description;
   }
}

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

public enum SimpleEnum
{
   [EnumDescription("Today")]
   Today,

   [EnumDescription("Last 7 days")]
   Last7,

   [EnumDescription("Last 14 days")]
   Last14,

   [EnumDescription("Last 30 days")]
   Last30,

   [EnumDescription("All")]
   All
}

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.

/// <span class="code-SummaryComment"><summary></span>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// <span class="code-SummaryComment"></summary></span>
public static class EnumHelper
{
   /// <span class="code-SummaryComment"><summary></span>
   /// Gets the <span class="code-SummaryComment"><see cref="DescriptionAttribute" /> of an <see cref="Enum" /></span>
   /// type value.
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><param name="value">The <see cref="Enum" /> type value.</param></span>
   /// <span class="code-SummaryComment"><returns>A string containing the text of the</span>
   /// <span class="code-SummaryComment"><see cref="DescriptionAttribute"/>.</returns></span>
   public static string GetDescription(Enum value)
   {
      if (value == null)
      {
         throw new ArgumentNullException("value");
      }

      string description = value.ToString();
      FieldInfo fieldInfo = value.GetType().GetField(description);
      EnumDescriptionAttribute[] attributes =
         (EnumDescriptionAttribute[])
       fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);

      if (attributes != null && attributes.Length > 0)
      {
         description = attributes[0].Description;
      }
      return description;
   }

   /// <span class="code-SummaryComment"><summary></span>
   /// Converts the <span class="code-SummaryComment"><see cref="Enum" /> type to an <see cref="IList" /> </span>
   /// compatible object.
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><param name="type">The <see cref="Enum"/> type.</param></span>
   /// <span class="code-SummaryComment"><returns>An <see cref="IList"/> containing the enumerated</span>
   /// type value and description.<span class="code-SummaryComment"></returns></span>
   public static IList ToList(Type type)
   {
      if (type == null)
      {
         throw new ArgumentNullException("type");
      }

      ArrayList list = new ArrayList();
      Array enumValues = Enum.GetValues(type);

      foreach (Enum value in enumValues)
      {
         list.Add(new KeyValuePair<Enum, string>(value, GetDescription(value)));
      }

      return list;
   }
}

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:

ComboBox combo = new ComboBox();
combo.DataSource = EnumHelper.ToList(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "Key";

.NET 3.5 Extension Methods

As was pointed out in the article comments[^], it is very easy to convert the methods in EnumHelper to be extension methods in .NET 3.5. In order to do this, you will need to have either the ..NET Framework 3.5 Beta 2[^] or Visual Studio 2008[^] installed.

To make the change to extension methods, you add the this keyword to the first parameter in ToList and GetDescription. The update class should look like:

/// <span class="code-SummaryComment"><summary></span>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// <span class="code-SummaryComment"></summary></span>
public static class EnumHelper
{
   /// <span class="code-SummaryComment"><summary></span>
   /// Gets the <span class="code-SummaryComment"><see cref="DescriptionAttribute" /> of an <see cref="Enum" /> </span>
   /// type value.
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><param name="value">The <see cref="Enum" /> type value.</param></span>
   /// <span class="code-SummaryComment"><returns>A string containing the text of the</span>
   /// <span class="code-SummaryComment"><see cref="DescriptionAttribute"/>.</returns></span>
   public static string GetDescription(this Enum value)
   {
      if (value == null)
      {
         throw new ArgumentNullException("value");
      }

      string description = value.ToString();
      FieldInfo fieldInfo = value.GetType().GetField(description);
      EnumDescriptionAttribute[] attributes =
         (EnumDescriptionAttribute[])
       fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);

      if (attributes != null && attributes.Length > 0)
      {
         description = attributes[0].Description;
      }
      return description;
   }

   /// <span class="code-SummaryComment"><summary></span>
   /// Converts the <span class="code-SummaryComment"><see cref="Enum" /> type to an <see cref="IList" /> </span>
   /// compatible object.
   /// <span class="code-SummaryComment"></summary></span>
   /// <span class="code-SummaryComment"><param name="type">The <see cref="Enum"/> type.</param></span>
   /// <span class="code-SummaryComment"><returns>An <see cref="IList"/> containing the enumerated</span>
   /// type value and description.<span class="code-SummaryComment"></returns></span>
   public static IList ToList(this Type type)
   {
      if (type == null)
      {
         throw new ArgumentNullException("type");
      }

      ArrayList list = new ArrayList();
      Array enumValues = Enum.GetValues(type);

      foreach (Enum value in enumValues)
      {
         list.Add(new KeyValuePair<Enum, string>(value, GetDescription(value)));
      }

      return list;
   }
}

In order to bind the ComboBox, you simply change the way you set the DataSource property to do the following:

combo.DataSource = typeof(SimpleEnum).ToList();

Advanced Uses

To support additional advanced cases where the underlying numeric value is needed, you can use a generic version of the ToList method. This method requires that the type argument be explicitly specified, like this:

// .NET 2.0, 3.0
combo.DataSource = EnumHelper.ToList<int>(typeof(SimpleEnum));

// .NET 3.5
combo.DataSource = typeof(SimpleEnum).ToList<int>();

In addition, by using the ToExtendedList<T> method, you can retrieve an IList of KeyValueTriplet<Enum, T, string> instances that hold the enum value (the key), the numeric value, and the description (the value). This allows the most flexibility in deciding what data type will be bound to the ValueMember property. You can use the ToExtendedList<T> in the following ways:

// .NET 2.0, 3.0
combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "Key";

// OR

combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "NumericKey";

// .NET 3.5
combo.DataSource = typeof(SimpleEnum).ToExtendedList<int>();
combo.DisplayMember = "Value";
combo.ValueMember = "Key";

// OR

combo.DataSource = typeof(SimpleEnum).ToExtendedList<int>();
combo.DisplayMember = "Value";
combo.ValueMember = "NumericKey";

Conclusion

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.)

Revision History

6-September-2007:

  • Added additional error handling to ToList.
  • Created a generic ToList method that allows the numeric value of the enum to be used as the key.
  • Added a KeyValueTriplet struct that allows the enum value, numeric value, and description to be returned.
  • Added a generic ToExtendedList method that allows the enum value, numeric value, and description to be returned using a KeyValueTriplet.
  • Cleaned up the project structure to remove duplicate files.

26-August-2007:

  • Added information about using the EnumHelper methods as extension methods in .NET 3.5.
  • Added a new download for .NET 3.5
  • Added a tester application to the download for .NET 2.0

12-August-2007:

  • Original article.

License

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

About the Author

Scott Dorman
Software Developer (Senior)
United States United States
Scott is a C# MVP and author who has been involved with computers in one way or another for as long as he can remember, but started professionally in 1993. He has worked at Fortune 500 companies and privately held start-ups focused on IT consulting where he gained experience in embedded systems design and software development to systems administration and database programming, and everything in between.
 
After spending 6 years as a systems administrator, Scott started developing eCommerce store fronts. Since 2001, he has worked on many different projects using .NET and C#. Although his primary focus right now is commercial software applications, he prefers building infrastructure components, reusable shared libraries and helping companies define, develop and automate process standards and guidelines.
 
Scott runs a software architecture-focused user group, speaks extensively, and contributes regularly to online communities such as The Code Project and StackOverflow, and is the Community Manager and Senior Editor for DotNetKicks.
Follow on   Twitter

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralVery nice codememberTim Callaghan17-Nov-09 19:01 
GeneralVB.NET Translation [modified]memberkelvin199728-Aug-09 23:11 
3 Simple Steps...
 
1. In your WinForm (assuming you have made a ComboBox named comboMouse in design view):
 
        comboMouse.DataSource = EnumHelper.ToList(GetType(MouseEnum))
        comboMouse.DisplayMember = "Value"
        comboMouse.ValueMember = "Key"
 
2. Create some MouseEnum:
 
    Public Enum MouseEnum
        <EnumDescription("Single Click")> Mouse_Single_Click = 1
        <EnumDescription("Double Click")> Mouse_Double_Click = 2
        <EnumDescription("Right Click")> Mouse_Right_Click = 3
    End Enum
 
3. Create a file, put this in:
 
Imports System.Reflection 'for FieldInfo

<AttributeUsage(AttributeTargets.Enum Or AttributeTargets.Field, AllowMultiple:=False)> _
Public NotInheritable Class EnumDescriptionAttribute
    Inherits Attribute
    Private _description As String
    Public Property Description() As String
        Get
            Return _description
        End Get
        Set(ByVal value As String)
            _description = value
        End Set
    End Property
    Public Sub New(ByVal description As String)
        MyBase.New()
        _description = description
    End Sub
End Class
 
Public Class EnumHelper
    Public Shared Function GetDescription(ByVal value As [Enum]) As String
        If IsNothing(value) Then Throw New ArgumentNullException("value")
        Dim description As String = value.ToString()
        Dim fieldInfo As FieldInfo = value.GetType().GetField(description)
        Dim attributes() As EnumDescriptionAttribute = _
        CType(fieldInfo.GetCustomAttributes(GetType(EnumDescriptionAttribute), False),  _
        EnumDescriptionAttribute())
        If Not IsNothing(attributes) AndAlso attributes.Length > 0 Then _
            description = attributes(0).Description
        Return description
    End Function
    Public Shared Function ToList(ByVal type As Type) As IList
        If IsNothing(type) Then Throw New ArgumentNullException("type")
        Dim list As New ArrayList
        Dim enumValues As Array = [Enum].GetValues(type)
        For Each value As [Enum] In enumValues
            list.Add(New KeyValuePair(Of [Enum], String)(value, GetDescription(value)))
        Next
        Return list
    End Function
End Class
 
And you are good to go!

Rose | [Rose] Laugh | :laugh:
 
modified on Saturday, August 29, 2009 5:39 AM

GeneralA little different approach same resultmemberRob241219-Jul-09 9:26 
GeneralFor those stuck with .NET 1.1 ...memberketchupy17-May-09 12:32 
GeneralElegantmemberRi Qen-Sin30-Dec-07 4:40 
GeneralRe: ElegantmemberScott Dorman30-Dec-07 5:37 
QuestionSet the DataSource before Display/ValueMembermemberCraig Darin Cote3-Nov-07 20:05 
AnswerRe: Set the DataSource before Display/ValueMembermemberScott Dorman4-Nov-07 1:38 
GeneralMinor simplificationmemberrendle2-Oct-07 2:01 
GeneralRe: Minor simplificationmemberScott Dorman2-Oct-07 2:51 
GeneralCool idea indeedmemberrochana_bg20-Sep-07 13:28 
GeneralRe: Cool idea indeedmemberScott Dorman20-Sep-07 18:17 
GeneralCool Ideamembermerlin9817-Sep-07 4:37 
GeneralRe: Cool IdeamemberScott Dorman23-Sep-07 8:53 
QuestionLocalization?memberUrs Enzler6-Sep-07 23:14 
AnswerRe: Localization?memberwout de zeeuw6-Sep-07 23:29 
GeneralRe: Localization?memberUrs Enzler6-Sep-07 23:34 
GeneralRe: Localization?memberScott Dorman7-Sep-07 2:49 
AnswerRe: Localization?memberScott Dorman7-Sep-07 2:46 
GeneralRe: Localization?memberUrs Enzler7-Sep-07 2:53 
GeneralRe: Localization?memberScott Dorman7-Sep-07 3:12 
QuestionKey integer value?memberGeorge Fitch6-Sep-07 0:05 
AnswerRe: Key integer value?memberScott Dorman6-Sep-07 1:55 
GeneralRe: Key integer value? [modified]memberGeorge Fitch6-Sep-07 12:18 
GeneralRe: Key integer value?memberScott Dorman6-Sep-07 14:07 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130617.1 | Last Updated 30 Dec 2007
Article Copyright 2007 by Scott Dorman
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid