65.9K
CodeProject 正在变化。 阅读更多。
Home

绑定带描述的枚举

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (66投票s)

2007年8月12日

CPOL

4分钟阅读

viewsIcon

315809

downloadIcon

2613

使用数据绑定将枚举绑定到 UI 控件的简单解决方案。

引言

我时不时需要将枚举类型绑定到 Windows Forms 控件,通常是 ComboBox。CodeProject 上有很多文章介绍了各种方法,各有优缺点。然而,它们通常比实际需要更复杂,在某些情况下,需要开发人员实现枚举、使用枚举或两者都要付出大量精力。

简单方法

最简单的方法是使用 Enum.GetValues() 方法,将其结果设置为 ComboBoxDataSource 属性。如果你有以下枚举:

public enum SimpleEnum
{
    Today,
    Last7
    Last14,
    Last30,
    All
}

你可以像这样将其绑定到 ComboBox

ComboBox combo = new ComboBox();
combo.DataSource = Enum.GetValues(typeof(SimpleEnum));

虽然这确实有效,但存在几个问题:

  1. 不支持本地化。
  2. 不支持更易读的描述。

绑定带描述

幸运的是,使用一点点反射并将枚举值装饰有特性,有一种相对简单的方法可以满足这些要求。你不需要很多泛型类、自定义类或自定义类型描述符……只需两个静态方法,它们都少于 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 关键字添加到 ToListGetDescription 的第一个参数中。更新后的类应如下所示:

/// <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日

  • 原始文章。
© . All rights reserved.