Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DisplayNameAttribute for Enumerations

3.67/5 (12 votes)
21 Aug 2008CPOL3 min read 1   1.2K  
An article about the creation of a DisplayNameAttribute for fields of enumerations in .NET which value will be display in a PropertyGrid.
FieldDisplayNameAttribute.gif

Introduction

The .NET Framework is missing a DisplayNameAttribute class for field members of an enumeration. The DisplayNameAttribute class can be used for class properties but it doesn't support a display name for fields. It is difficult to use the PropertyGrid control for end user dialogs with some meaningful values in a combobox for a property with an enumeration type. There will be shown the original values which are only useful for developers of source code.

Using the Code

First we start with the attribute class. It is pretty simple because we use the .NET class DisplayNameAttribute as the base class which provides all the needed functionality. The only change we have to make is the AttributeTargets which is set to field.

C#
[System.AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldDisplayNameAttribute : System.ComponentModel.DisplayNameAttribute
...

The new attribute class is used by the enumeration which should display nicely.

C#
public enum Driver
{
   /// <summary>
   /// unknown
   /// </summary>

   [FieldDisplayName("Not defined")]
   Unknown,

   /// <summary>
   /// driver for MS SQL Server
   /// </summary>

   [FieldDisplayName(".Net Provider for Microsoft SQL Server")]
   MsSqlClient,
...

At the moment the PropertyGrid control would not recognize the new attribute class and doesn't show the display name. We need a new TypeConverter which can translate between the values of the enumeration and the display names.

C#
/// <summary>
/// TypeConverter for enum types which supports the FieldDisplayName-attribute
/// </summary>

public class EnumTypeConverter : EnumConverter
{
...

The class EnumTypeConverter supports the translation between all types of enumeration and the type string.

C#
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
   //
   // we support converting between string type an enum
   //
   if (sourceType == typeof(string))
      return true;
   return base.CanConvertFrom(context, sourceType);
}

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
   //
   // we support converting between string type an enum
   //
   if (destinationType == typeof(string))
      return true;
   return base.CanConvertTo(context, destinationType);
}

The translation is done by mapping tables which are built once for every enumeration type when it is needed. The mapping tables are build via reflection. The code iterates through the field member of the enumeration and looks for the FieldDisplayNameAttribute. If the attribute is missing the converter uses the ToString() value for mapping purposes.

The second version of the EnumTypeConverter adds support for resource managers. You can provide the display names via resource files for different languages. The definitions in resource files overwrite the value of the FieldDisplayNameAttribute. The key for the resource strings have to be the full type name of the enumeration type followed by "_<enumeration value>". The points within the full type name have to be replaced by underscore. Example: System_Windows_Forms_BorderStyle_None

C#
public override object ConvertTo(ITypeDescriptorContext context,
    System.Globalization.CultureInfo culture, object value, Type destinationType)
{
  object result = value;

   //
   // source value have to be a enumeration type or string, destination a string type
   //
   if (destinationType == typeof(string) &&
       value != null)
   {
      if (value.GetType().IsEnum)
      {
         // ensure that the mapping table is available for the enumeration type
         EnsureMappingsAvailable(value.GetType(), culture);
         MappingContainer container = mappings[value.GetType()];
         MappingPerCulture mapping = container.mappingsPerCulture[culture];
         string valueStr = value.ToString();

         if (mapping.fieldDisplayNameFound)
         {
            if (valueStr.IndexOf(',') < 0)
            {
               // simple enum value
               if (mapping.mappings.ContainsKey(valueStr))
               {
                  result = mapping.mappings[valueStr];
               }
               else
               {
                  throw GetConvertToException(valueStr, destinationType);
               }
            }
            else
            {
               // flag enum with more then one enum value
               string[] parts = valueStr.Split(new string[] { ", " },
                   StringSplitOptions.RemoveEmptyEntries);
               System.Text.StringBuilder builder = new System.Text.StringBuilder();
               string tmp;
               for (int index = 0; index < parts.Length; index++)
               {
                  tmp = parts[index];
                  if (mapping.mappings.ContainsKey(tmp))
                  {
                     builder.Append(mapping.mappings[tmp]);
                     builder.Append(", ");
                  }
                  else
                  {
                     throw GetConvertToException(valueStr, destinationType);
                  }
               }

               builder.Length -= 2;
               result = builder.ToString();
            }
         }
         else
         {
            result = value.ToString();
         }
      }
   }
   else
   {
      result = base.ConvertTo(context, culture, value, destinationType);
   }

   return result;
}

But we also want a combobox in the PropertyGrid control. It doesn't exist if the EnumTypeConverter isn't extended in the right way. For a combobox the PropertyGrid control need some standard values. The TypeConverter support three methods for that problem. The first methods tells generally if the type supports standard values. Of course all enumeration have standard values.

C#
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
   //
   // enumerations support standard values which are a list of the fields of
   // the enumeration
   //
   return true;
}

The second method tells if the values are exclusive values. All enumerations values are exclusive. There will be no other values excepted.

C#
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
   //
   // all standard values of an enumeration are exclusiv values, no others are allowed
   //
   return true;
}

The most important method provides the list of the standard values. The list has to include the original values of the enumeration not the translated display name values. That is important because the translation of the standard values is done in the normal way.

C#
public override TypeConverter.StandardValuesCollection GetStandardValues(
    ITypeDescriptorContext context)
{
   // ensure that the mapping table is available for the enumeration type
   // it builds the standard value collection too
   EnsureMappingsAvailable(EnumType, CultureInfo.CurrentCulture);
   MappingContainer container = mappings[EnumType];
   // wrap it with the right type. it is also possible to use Enum.GetValues
   TypeConverter.StandardValuesCollection values = new StandardValuesCollection(
       container.standardValues);
   return values;
}

Now we have only one thing to do. We have to decorate our enumeration example with an attribute that tells all interested controls and classes to use our type converter class.

C#
[TypeConverter(typeof(EnumTypeConverter))]
public enum Driver
{
...

Since version 2 of this sample you can use the EnumTypeDescriptionProvider. Create an instance and the EnumTypeConverter will be used for every enum type without the need of the TypeConverter attribute.

That's all. A sample dialog with a PropertyGrid control and a sample class which uses the enumeration Driver completes the example.

Conclusion

If you want to use that solution you have only to do the following things:

  • add the classes FieldDisplayNameAttribute and EnumTypeConverter to your project
  • add the attribute [TypeConverter(typeof(EnumTypeConverter))] to your enumeration
  • add the attribute [FieldDisplayName("any meaningful display name")] to the field of your enumeration

For the extended version use the following steps:

  • add all necessary classes to your project
  • create an instance of EnumTypeDescriptionProvider
  • register the ResourceManager by calling the function EnumTypeConverter.RegisterResourceManager

History

Version 2

  • Changed public class EnumTypeConverter : TypeConverter to public class EnumTypeConverter : EnumConverter
  • Fixed support for Flags enumerations
  • Added TypeDescriptor for enumerations which adds support for enumerations without the TypeConverter/FieldDisplayName attribute
  • Added support for resource managers and different languages

Version 1

  • first release

License

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