枚举的 DisplayNameAttribute
一篇关于为.NET中枚举字段创建DisplayNameAttribute的文章,该值将显示在PropertyGrid中。

引言
.NET Framework 缺少用于枚举字段成员的 DisplayNameAttribute
类。 DisplayNameAttribute
类可以用于类属性,但它不支持字段的显示名称。在 PropertyGrid
控件中,对于具有枚举类型的属性,使用一些有意义的值在组合框中为最终用户对话框显示比较困难。 将会显示原始值,而这些值仅对源代码的开发人员有用。
Using the Code
首先,我们从属性类开始。它非常简单,因为我们使用 .NET 类 DisplayNameAttribute
作为基类,该类提供了所有需要的功能。我们唯一需要做的更改是将 AttributeTargets
设置为 field。
[System.AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldDisplayNameAttribute : System.ComponentModel.DisplayNameAttribute
...
新的属性类由应该很好地显示的枚举使用。
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,
...
目前,PropertyGrid
控件无法识别新的属性类,也不会显示显示名称。我们需要一个新的 TypeConverter
,它可以转换枚举的值和显示名称。
/// <summary>
/// TypeConverter for enum types which supports the FieldDisplayName-attribute
/// </summary>
public class EnumTypeConverter : EnumConverter
{
...
EnumTypeConverter
类支持所有枚举类型和字符串类型之间的转换。
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);
}
通过映射表进行翻译,该映射表在每次需要时为每种枚举类型构建一次。映射表通过反射构建。代码迭代枚举的字段成员并查找 FieldDisplayNameAttribute
。如果缺少该属性,则转换器使用 ToString()
值进行映射。
第二个版本的 EnumTypeConverter
添加了对资源管理器的支持。您可以为不同的语言通过资源文件提供显示名称。资源文件中的定义会覆盖 FieldDisplayNameAttribute
的值。资源字符串的键必须是枚举类型的完整类型名称,后跟“_<枚举值>”。完整类型名称中的点必须替换为下划线。示例:System_Windows_Forms_BorderStyle_None
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;
}
但是我们还想要 PropertyGrid
控件中的组合框。如果未以正确的方式扩展 EnumTypeConverter
,则它不存在。对于组合框,PropertyGrid
控件需要一些标准值。 TypeConverter
支持针对该问题的三种方法。第一种方法通常告诉该类型是否支持标准值。当然,所有枚举都有标准值。
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
//
// enumerations support standard values which are a list of the fields of
// the enumeration
//
return true;
}
第二种方法说明这些值是否为互斥值。所有枚举值都是互斥的。除了这些值之外,不会有其他值。
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
//
// all standard values of an enumeration are exclusiv values, no others are allowed
//
return true;
}
最重要的方法提供了标准值的列表。该列表必须包括枚举的原始值,而不是已翻译的显示名称值。这一点很重要,因为标准值的翻译以正常方式进行。
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;
}
现在我们只有一件事要做。我们必须用一个属性来修饰我们的枚举示例,该属性告诉所有感兴趣的控件和类使用我们的类型转换器类。
[TypeConverter(typeof(EnumTypeConverter))]
public enum Driver
{
...
从该示例的第 2 版开始,您可以使用 EnumTypeDescriptionProvider
。创建一个实例,EnumTypeConverter
将用于每个枚举类型,而无需 TypeConverter
属性。
就这样。一个带有 PropertyGrid
控件的示例对话框和一个使用枚举 Driver
的示例类完成了该示例。
结论
如果您想使用该解决方案,则只需执行以下操作
- 将类
FieldDisplayNameAttribute
和EnumTypeConverter
添加到您的项目中 - 将属性
[TypeConverter(typeof(EnumTypeConverter))]
添加到您的枚举中 - 将属性
[FieldDisplayName("任何有意义的显示名称")]
添加到枚举的字段中
对于扩展版本,请使用以下步骤
- 将所有必要的类添加到您的项目中
- 创建
EnumTypeDescriptionProvider
的一个实例 - 通过调用函数
EnumTypeConverter.RegisterResourceManager
注册ResourceManager
历史
版本 2
- 将
public class EnumTypeConverter : TypeConverter
更改为public class EnumTypeConverter : EnumConverter
- 修复了对 Flags 枚举的支持
- 为枚举添加了 TypeDescriptor,该描述符添加了对没有 TypeConverter/FieldDisplayName 属性的枚举的支持
- 添加了对资源管理器和不同语言的支持
版本 1
- 首次发布