Enum: Binding to the Description Attribute

Posted Fri, Jul 10 2009 16:31 by Deborah Kurata

The Enum keyword allows you to define a standard set of named constants for use in your application. Sometimes you may want to present this same list of  values to your user.

You can display the set of Enum values in a ComboBox or ListBox using data binding. And even better, if you use the DescriptionAttribute in the Enum, you can display a more user-friendly set of values.

For example, a Customer business object may have a CustomerType defined with a specific set of options for corporations, individuals, schools, and charities. You want to use these values in your code, but also display them to the user in a ComboBox.

Enum Definition

The Enum can reside in the Customer class, or in its own class.

In C#:

public enum CustomerTypeOption
{
    NotDefined, 
    Corp, 
    IndividualorFamily, 
    SchoolorUniversity, 
    Charity
}

In VB:

Public Enum CustomerTypeOption
   NotDefined
   Corp
   IndividualorFamily
   SchoolorUniversity
   Charity
End Enum

CustomerType Property

A CustomerType property in the Customer class can use this enum type.

In C#:

public CustomerTypeOption CustomerType { get; set; }

In VB:

Private _CustomerType As CustomerTypeOption
Public Property CustomerType() As CustomerTypeOption
    Get
        Return _CustomerType
    End Get
    Set(ByVal value As CustomerTypeOption)
        _CustomerType = value
    End Set
End Property

The C# code uses the automatically implemented properties feature introduced with .NET 3.5. The VB code uses the full property definition.

Binding To an Enum

The Enum can also be used by the user interface. For example, you can bind a comboBox or listBox to a set of customer type options.

In C#:

Dictionary<string, int> CustomerTypeList =
                           new Dictionary<string, int>();

foreach (int enumValue in
             Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    CustomerTypeList.Add(
        
Enum.GetName(typeof(Customer.CustomerTypeOption),
                                      enumValue ), enumValue);
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
                                                                null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

For Each enumValue As Integer In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    CustomerTypeList.Add( _
       [Enum].GetName(GetType(Customer.CustomerTypeOption), _
                                        enumValue), enumValue)
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                               Nothing)

This code defines a Dictionary to contain the name of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values and adds the name and value to the dictionary.

The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.

The result looks like this:

image

While it does provide the appropriate options, it is not very user-friendly. It would be nicer if there were appropriate spaces and full words. For that, you can use the DescriptionAttribute on the Enum.

Using the DescriptionAttribute

NOTE: Be sure to import the System.ComponentModel and the System.Reflection namespaces.

In C#:

public enum CustomerTypeOption
{
    [DescriptionAttribute("Not Specified")]
    NotDefined,

    [DescriptionAttribute("Corporation")]
    Corp,

    [DescriptionAttribute("Individual or Family")]
    IndividualorFamily,

    [DescriptionAttribute("School or University")]
    SchoolorUniversity,

    [DescriptionAttribute("Charity")]
    Charity
}

In VB:

Public Enum CustomerType
    <DescriptionAttribute("Corporate Customer")> _
    CorpCustomer

    <DescriptionAttribute("Individual or Family")> _
    IndividualorFamily

    <DescriptionAttribute("School or University")> _
    SchoolorUniversity

    <DescriptionAttribute("Charity")> _
    Charity

    <DescriptionAttribute("Kingdom")> _
    Kingdom
End Enum

Binding to an Enum Description

Now you can bind to the description instead of to the Enum constant.

In C#:

Dictionary<string, int> CustomerTypeList =
                                new Dictionary<string, int>();

FieldInfo fi;
DescriptionAttribute da;
foreach (Customer.CustomerTypeOption enumValue in
           Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    fi = typeof(Customer.CustomerTypeOption).
                    GetField((enumValue.ToString()));
    da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
                    typeof(DescriptionAttribute));
    if (da != null)
    {
       CustomerTypeList.Add(da.Description, (int)enumValue);
    }
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
                                                              null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

Dim fi As FieldInfo
Dim da As DescriptionAttribute
For Each enumValue As Customer.CustomerTypeOption In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    fi = GetType(Customer.CustomerTypeOption). _
                GetField(enumValue.ToString)
    da = DirectCast(Attribute.GetCustomAttribute(fi, _
                GetType(DescriptionAttribute)), DescriptionAttribute)
    If da IsNot Nothing Then
        CustomerTypeList.Add(da.Description, enumValue)
    End If
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                              Nothing)

This code defines a Dictionary to contain the description of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values. It uses reflection to find the DescriptionAttribute. It then adds the description and value to the dictionary.

The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.

Now the combo box looks like this:

image

Much better!

Extension Methods

So the user interface looks nicer, but the code to get the DescriptionAttribute looks rather tedious. It would be much nicer if there was a GetDescription method on the Enum. But hey, with the extension method feature introduced in .NET 3.5, we can build one ourselves!

In C#:

public static string GetDescription(this Enum currentEnum)
{
    string description = String.Empty;
    DescriptionAttribute da ;

    FieldInfo fi = currentEnum.GetType().
                GetField(currentEnum.ToString());
    da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, 
               
typeof(DescriptionAttribute));
    if (da != null)
        description = da.Description;
    else
        description = currentEnum.ToString();

    return description;
}

In VB:

<ExtensionAttribute()> _
Public Function GetDescription(ByVal currentEnum As [Enum]) As String
    Dim description As String = String.Empty
    Dim da As DescriptionAttribute

    Dim fi As FieldInfo = currentEnum.GetType. _
                GetField(currentEnum.ToString)
    da = DirectCast(Attribute.GetCustomAttribute(fi, _
                GetType(DescriptionAttribute)), DescriptionAttribute)
    If da IsNot Nothing Then
        description = da.Description
    Else
        description = currentEnum.ToString
    End If

    Return description
End Function

The C# code can reside in any static class. The VB code must reside in a module, which provides the same features as a static class.

This code uses reflection to get the DescriptionAttribute for any Enum value. So it can be readily reused. The code returns the DescriptionAttribute if one is found, otherwise it returns the name.

Binding to an Enum Description - Redux

Now the binding code is much easier to work with.

In C#:

Dictionary<string, int> CustomerTypeList =
                         new Dictionary<string, int>();

foreach (Customer.CustomerTypeOption enumValue in
           Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    CustomerTypeList.Add(enumValue.GetDescription(), (int)enumValue);
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList, 
                                                              null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

For Each enumValue As Customer.CustomerTypeOption In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    CustomerTypeList.Add(enumValue.GetDescription, enumValue)
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                              Nothing)

Enjoy!

Comments

# re: Enum: Binding to the Description Attribute

Saturday, July 11, 2009 2:49 PM by paul

Great !

How using this in a multi-language context?

# re: Enum: Binding to the Description Attribute

Sunday, July 12, 2009 6:47 AM by Moggoly

I wrote about this same solution over a year ago - with a slight difference in that my solution is using a generic extension method to get the description attribute value.

# re: Enum: Binding to the Description Attribute

Monday, July 13, 2009 12:15 PM by Deborah Kurata

Hi Moggoly -

Thank you for your post. My extension method just extended [Enum]. It would be interesting to see how you did it with a generic extension method. Would you be willing to post some of your code?

Thanks again!

# re: Enum: Binding to the Description Attribute

Thursday, July 16, 2009 7:57 AM by Yann

Hi Deborah,

Thanks, this was just what I needed!

However, instead of using a dictionary as you did, I used LINQ with an anonymous type.

           Dim enums = [Enum].GetValues(_type)

           result = _

               ( _

                   From e As Object In enums _

                   Select e = New With _

                       { _

                             .ID = CInt(e) _

                           , .Name = CType(e, [Enum]).GetDescription _

                       } _

                   Order By _

                       e.Name _

               ).ToList

I had already created an extension method "ToWords" for the string type (which I didn't show here for simplicity), but I needed to have "Week 1/2", "Week 3/4" which of course I couldn't have as enum members.

So your code helped me out enormously!

Thanks,

Yann

# re: Enum: Binding to the Description Attribute

Thursday, July 16, 2009 6:19 PM by Deborah Kurata

Very cool, Yann.

Thanks!

# re: Enum: Binding to the Description Attribute

Tuesday, July 21, 2009 5:14 PM by Deborah Kurata

Hi Paul -

If you have to support multiple languages in your UI, you should not have any strings that you plan to display to the user hard-coded in the code.

So you won't be able to leverage this technique.

# re: Enum: Binding to the Description Attribute

Thursday, July 30, 2009 1:21 PM by Rob

Deborah,

your solution is lacking of a sort (orderby) metod. Check my ListKeyValuePairEx which you can change for Dictionaries as well:

To use:

<combobox>.BindEnumKeyValue(typeof(<enum>));

or

<combobox>.BindEnumDescriptionValue(typeof(<enum>));

public static class ComboBoxEx

   {

       public static void BindEnumKeyValue(this ComboBox obj, Type enumType)

       {

           obj.DataSource = enumType.EnumToList();

           obj.DisplayMember = "Key";

           obj.ValueMember = "Value";

       }

       public static void BindEnumDescriptionValue(this ComboBox obj, Type enumType)

       {

           obj.DataSource = enumType.EnumDescriptionToList();

           obj.DisplayMember = "Key";

           obj.ValueMember = "Value";

       }

}

public static class EnumEx

   {

       public static IList EnumToList(this Type enumType)

       {

           List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();

           foreach (Enum key in Enum.GetValues(enumType))

           {

               int value = (int)Enum.Parse(enumType, key.ToString());

               list.AddUnique(new KeyValuePair<string, int>(key.ToString(), value));

           }

           return list.SortByKey();

       }

       public static IList EnumDescriptionToList(this Type enumType)

       {

           List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();

           foreach (Enum key in Enum.GetValues(enumType))

           {

               int value = (int)Enum.Parse(enumType, key.ToString());

               list.AddUnique(new KeyValuePair<string, int>(key.GetEnumDescription(), value));

           }

           return list.SortByKey();

       }

       public static string GetEnumDescription(this Enum value)

       {

           return GetEnumAttribute(value, typeof(DescriptionAttribute));

       }

       public static string GetEnumCategory(this Enum value)

       {

           return GetEnumAttribute(value, typeof(CategoryAttribute));

       }

       public static string GetEnumAttribute(this Enum value, Type attribute)

       {

           Type type = value.GetType();

           if (value == null)

           {

               throw new ArgumentException("Value must be of type Enum", "Value");

           }

           System.Reflection.MemberInfo[] memberInfo = type.GetMember(value.ToString());

           if (memberInfo != null && memberInfo.Length > 0)

           {

               object[] attrs = memberInfo[0].GetCustomAttributes(attribute, false);

               if (attrs != null && attrs.Length > 0)

               {

                   return ((DescriptionAttribute)attrs[0]).Description;

               }

           }

           return value.ToString();

       }

   }

public static class ListKeyValuePairEx

   {

       public static List<KeyValuePair<K, V>> AddUnique<K, V>(this List<KeyValuePair<K, V>> list, KeyValuePair<K, V> kv)

       {

           if (!list.Contains(kv))

               list.Add(kv);

           return list;

       }

       public static List<KeyValuePair<K, V>> SortByKey<K, V>(this List<KeyValuePair<K, V>> list)

       {

           List<KeyValuePair<K, V>> sorted = new List<KeyValuePair<K, V>>();

           foreach (KeyValuePair<K, V> item in list.OrderBy(key => key.Key))

           {

               sorted.Add(new KeyValuePair<K, V>(item.Key, item.Value));

           }

           return sorted;

       }

       public static List<KeyValuePair<K, V>> SortByValue<K, V>(this List<KeyValuePair<K, V>> list)

       {

           List<KeyValuePair<K, V>> sorted = new List<KeyValuePair<K, V>>();

           foreach (KeyValuePair<K, V> item in list.OrderBy(key => key.Value))

           {

               sorted.Add(new KeyValuePair<K, V>(item.Key, item.Value));

           }

           return sorted;

       }

   }

Leave a Comment

(required) 
(required) 
(optional)
(required)