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:
- There is no support for localization.
- 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
.
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field,
AllowMultiple = false)]
public sealed class EnumDescriptionAttribute : Attribute
{
private string description;
public string Description
{
get
{
return this.description;
}
}
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.
public static class EnumHelper
{
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;
}
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:
public static class EnumHelper
{
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;
}
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:
combo.DataSource = EnumHelper.ToList<int>(typeof(SimpleEnum));
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:
combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "NumericKey";
combo.DataSource = typeof(SimpleEnum).ToExtendedList<int>();
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
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: