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.
[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.
public enum Driver
{
[FieldDisplayName("Not defined")]
Unknown,
[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.
public class EnumTypeConverter : EnumConverter
{
...
The class EnumTypeConverter
supports the translation between all types of enumeration and the type string.
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
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
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
object result = value;
if (destinationType == typeof(string) &&
value != null)
{
if (value.GetType().IsEnum)
{
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)
{
if (mapping.mappings.ContainsKey(valueStr))
{
result = mapping.mappings[valueStr];
}
else
{
throw GetConvertToException(valueStr, destinationType);
}
}
else
{
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.
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
The second method tells if the values are exclusive values. All enumerations values are exclusive. There will be no other values excepted.
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
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.
public override TypeConverter.StandardValuesCollection GetStandardValues(
ITypeDescriptorContext context)
{
EnsureMappingsAvailable(EnumType, CultureInfo.CurrentCulture);
MappingContainer container = mappings[EnumType];
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.
[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