绑定带描述的枚举






4.46/5 (66投票s)
使用数据绑定将枚举绑定到 UI 控件的简单解决方案。
引言
我时不时需要将枚举类型绑定到 Windows Forms 控件,通常是 ComboBox
。CodeProject 上有很多文章介绍了各种方法,各有优缺点。然而,它们通常比实际需要更复杂,在某些情况下,需要开发人员实现枚举、使用枚举或两者都要付出大量精力。
简单方法
最简单的方法是使用 Enum.GetValues()
方法,将其结果设置为 ComboBox
的 DataSource
属性。如果你有以下枚举:
public enum SimpleEnum
{
Today,
Last7
Last14,
Last30,
All
}
你可以像这样将其绑定到 ComboBox
:
ComboBox combo = new ComboBox();
combo.DataSource = Enum.GetValues(typeof(SimpleEnum));
虽然这确实有效,但存在几个问题:
- 不支持本地化。
- 不支持更易读的描述。
绑定带描述
幸运的是,使用一点点反射并将枚举值装饰有特性,有一种相对简单的方法可以满足这些要求。你不需要很多泛型类、自定义类或自定义类型描述符……只需两个静态方法,它们都少于 10 行代码。
第一步是为枚举添加描述特性。为了简单起见,你可以使用 System.ComponentModel.DescriptionAttribute
类,但我建议派生你自己的 EnumDescriptionAttribute
。
/// <summary>
/// Provides a description for an enumerated type.
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field,
AllowMultiple = false)]
public sealed class EnumDescriptionAttribute : Attribute
{
private string description;
/// <summary>
/// Gets the description stored in this attribute.
/// </summary>
/// <value>The description stored in the attribute.</value>
public string Description
{
get
{
return this.description;
}
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="EnumDescriptionAttribute"/> class.
/// </summary>
/// <param name="description">The description to store in this attribute.
/// </param>
public EnumDescriptionAttribute(string description)
: base()
{
this.description = description;
}
}
现在我们有了描述特性,我们需要用它来装饰枚举:
public enum SimpleEnum
{
[EnumDescription("Today")]
Today,
[EnumDescription("Last 7 days")]
Last7,
[EnumDescription("Last 14 days")]
Last14,
[EnumDescription("Last 30 days")]
Last30,
[EnumDescription("All")]
All
}
使用自定义特性允许你在定义枚举的代码中保留人类可读的描述。它还允许你检索描述的本地化版本。为了做到这一点,你只需要改变特性的查找方式,在字符串资源中查找适当的字符串。
接下来的部分是实际完成所有工作的。正如我提到的,这两个函数都少于 10 行代码。使用这些函数的最简单方法是创建一个静态(如果未使用 .NET 2.0 或更高版本,则为 sealed)类,其中包含这两个静态函数。
/// <summary>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// </summary>
public static class EnumHelper
{
/// <summary>
/// Gets the <see cref="DescriptionAttribute" /> of an <see cref="Enum" />
/// type value.
/// </summary>
/// <param name="value">The <see cref="Enum" /> type value.</param>
/// <returns>A string containing the text of the
/// <see cref="DescriptionAttribute"/>.</returns>
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;
}
/// <summary>
/// Converts the <see cref="Enum" /> type to an <see cref="IList" />
/// compatible object.
/// </summary>
/// <param name="type">The <see cref="Enum"/> type.</param>
/// <returns>An <see cref="IList"/> containing the enumerated
/// type value and description.</returns>
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;
}
}
如你所见,GetDescription
方法使用一点点反射来检索指定枚举值上的 EnumDescription 特性。如果找不到该特性,它将简单地使用该值名称。ToList
方法返回 KeyValuePair<Enum, string>
实例的 IList
,其中包含枚举值(键)和描述(值)。如果你未使用 .NET 2.0 或更高版本,则需要使用 DictionaryEntry
而不是 KeyValuePair<TKey, TValue>
。
为了绑定 ComboBox
,你现在需要这样做:
ComboBox combo = new ComboBox();
combo.DataSource = EnumHelper.ToList(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
.NET 3.5 扩展方法
正如文章 评论[^] 中指出的那样,将 EnumHelper
中的方法转换为 .NET 3.5 中的扩展方法非常容易。为了做到这一点,你需要安装 .NET Framework 3.5 Beta 2[^] 或 Visual Studio 2008[^]。
要将更改为扩展方法,请将 this
关键字添加到 ToList
和 GetDescription
的第一个参数中。更新后的类应如下所示:
/// <summary>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// </summary>
public static class EnumHelper
{
/// <summary>
/// Gets the <see cref="DescriptionAttribute" /> of an <see cref="Enum" />
/// type value.
/// </summary>
/// <param name="value">The <see cref="Enum" /> type value.</param>
/// <returns>A string containing the text of the
/// <see cref="DescriptionAttribute"/>.</returns>
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;
}
/// <summary>
/// Converts the <see cref="Enum" /> type to an <see cref="IList" />
/// compatible object.
/// </summary>
/// <param name="type">The <see cref="Enum"/> type.</param>
/// <returns>An <see cref="IList"/> containing the enumerated
/// type value and description.</returns>
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;
}
}
为了绑定 ComboBox
,你只需更改设置 DataSource
属性的方式,如下所示:
combo.DataSource = typeof(SimpleEnum).ToList();
高级用法
为了支持需要底层数字值的其他高级情况,你可以使用 ToList
方法的泛型版本。此方法要求显式指定类型参数,如下所示:
// .NET 2.0, 3.0
combo.DataSource = EnumHelper.ToList<int>(typeof(SimpleEnum));
// .NET 3.5
combo.DataSource = typeof(SimpleEnum).ToList<int>();
此外,通过使用 ToExtendedList<T>
方法,你可以检索 KeyValueTriplet<Enum, T, string>
实例的 IList
,其中包含枚举值(键)、数字值和描述(值)。这为你决定哪种数据类型将绑定到 ValueMember
属性提供了最大的灵活性。你可以按以下方式使用 ToExtendedList<T>
:
// .NET 2.0, 3.0
combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
// OR
combo.DataSource = EnumHelper.ToExtendedList<int>(typeof(SimpleEnum));
combo.DisplayMember = "Value";
combo.ValueMember = "NumericKey";
// .NET 3.5
combo.DataSource = typeof(SimpleEnum).ToExtendedList<int>();
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
// OR
combo.DataSource = typeof(SimpleEnum).ToExtendedList<int>();
combo.DisplayMember = "Value";
combo.ValueMember = "NumericKey";
结论
现在你有一个 ComboBox
,其值是你的枚举类型值,其显示是 EnumDescription
特性中指定的字符串。这适用于任何支持数据绑定的控件,包括 ToolStripComboBox
,尽管你需要将 ToolStripComboBox.Control
属性强制转换为 ComboBox
才能访问 DataSource
属性。(在这种情况下,当你引用选定值以将其作为枚举类型处理时,你还应该执行相同的强制转换。)
修订历史
2007年9月6日
- 为 ToList 添加了额外的错误处理。
- 创建了一个泛型 ToList 方法,允许使用枚举的数字值作为键。
- 添加了一个 KeyValueTriplet 结构,允许返回枚举值、数字值和描述。
- 添加了一个泛型 ToExtendedList 方法,允许使用 KeyValueTriplet 返回枚举值、数字值和描述。
- 清理了项目结构以删除重复文件。
2007年8月26日
- 添加了关于在 .NET 3.5 中将 EnumHelper 方法用作扩展方法的信息。
- 为 .NET 3.5 添加了新的下载。
- 为 .NET 2.0 下载添加了一个测试器应用程序。
2007年8月12日
- 原始文章。