我如何有一个枚举绑定组合框与自定义字符串格式的枚举值?

在后面的 Enum ToString中,描述了如下使用自定义属性 DescriptionAttribute的方法:

Enum HowNice {
[Description("Really Nice")]
ReallyNice,
[Description("Kinda Nice")]
SortOfNice,
[Description("Not Nice At All")]
NotNice
}

然后,调用函数 GetDescription,使用如下语法:

GetDescription<HowNice>(NotNice); // Returns "Not Nice At All"

但这对我没什么帮助。

我想要的东西有以下要求:

  • 读取 (HowNice)myComboBox.selectedItem将返回选定的值作为枚举值。
  • 用户应该看到用户友好的显示字符串,而不仅仅是枚举值的名称。因此,用户看到的不是“ NotNice”,而是“ Not Nice At All”。
  • 希望该解决方案只需对现有枚举进行最小限度的代码更改。

显然,我可以为我创建的每个枚举实现一个新的类,并覆盖它的 ToString(),但是对于每个枚举来说都有很多工作要做,我宁愿避免这样做。

有什么想法吗?

见鬼,我甚至会给你一个 拥抱作为奖励: -)

89845 次浏览

做到这一点的最好方法是建立一个类。

class EnumWithToString {
private string description;
internal EnumWithToString(string desc){
description = desc;
}
public override string ToString(){
return description;
}
}


class HowNice : EnumWithToString {


private HowNice(string desc) : base(desc){}


public static readonly HowNice ReallyNice = new HowNice("Really Nice");
public static readonly HowNice KindaNice = new HowNice("Kinda Nice");
public static readonly HowNice NotVeryNice = new HowNice("Really Mean!");
}

我相信这是最好的办法。

当填充在组合框中时,将显示漂亮的 ToString,而且没有人可以创建更多的类实例,这一事实本质上使它成为枚举。

可能需要稍微修改一下语法,我对 C # 不太在行

在 C # 中不可能覆盖枚举的 ToString () ,但是可以使用扩展方法;

public static string ToString(this HowNice self, int neverUsed)
{
switch (self)
{
case HowNice.ReallyNice:
return "Rilly, rilly nice";
break;
...

当然,您必须对该方法进行显式调用,即;

HowNice.ReallyNice.ToString(0)

这不是一个很好的解决方案,有一个开关语句和所有-但它应该工作,并希望白了许多重写..。

假设您不想为每个枚举创建一个类,我建议创建一个枚举值/显示文本的字典,然后绑定它。

注意,这依赖于原始文章中的 GetDescription 方法。

public static IDictionary<T, string> GetDescriptions<T>()
where T : struct
{
IDictionary<T, string> values = new Dictionary<T, string>();


Type type = enumerationValue.GetType();
if (!type.IsEnum)
{
throw new ArgumentException("T must be of Enum type", "enumerationValue");
}


//Tries to find a DescriptionAttribute for a potential friendly name
//for the enum
foreach (T value in Enum.GetValues(typeof(T)))
{
string text = value.GetDescription();


values.Add(value, text);
}


return values;
}

创建包含所需内容的集合(比如包含包含 HowNice枚举值的 Value属性的简单对象和包含 GetDescription<HowNice>(Value)Description属性,并将组合数据绑定到该集合)。

有点像这样:

Combo.DataSource = new EnumeratedValueCollection<HowNice>();
Combo.ValueMember = "Value";
Combo.DisplayMember = "Description";

当你有这样一个集合类:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;


namespace Whatever.Tickles.Your.Fancy
{
public class EnumeratedValueCollection<T> : ReadOnlyCollection<EnumeratedValue<T>>
{
public EnumeratedValueCollection()
: base(ListConstructor()) { }
public EnumeratedValueCollection(Func<T, bool> selection)
: base(ListConstructor(selection)) { }
public EnumeratedValueCollection(Func<T, string> format)
: base(ListConstructor(format)) { }
public EnumeratedValueCollection(Func<T, bool> selection, Func<T, string> format)
: base(ListConstructor(selection, format)) { }
internal EnumeratedValueCollection(IList<EnumeratedValue<T>> data)
: base(data) { }


internal static List<EnumeratedValue<T>> ListConstructor()
{
return ListConstructor(null, null);
}


internal static List<EnumeratedValue<T>> ListConstructor(Func<T, string> format)
{
return ListConstructor(null, format);
}


internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection)
{
return ListConstructor(selection, null);
}


internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection, Func<T, string> format)
{
if (null == selection) selection = (x => true);
if (null == format) format = (x => GetDescription<T>(x));
var result = new List<EnumeratedValue<T>>();
foreach (T value in System.Enum.GetValues(typeof(T)))
{
if (selection(value))
{
string description = format(value);
result.Add(new EnumeratedValue<T>(value, description));
}
}
return result;
}


public bool Contains(T value)
{
return (Items.FirstOrDefault(item => item.Value.Equals(value)) != null);
}


public EnumeratedValue<T> this[T value]
{
get
{
return Items.First(item => item.Value.Equals(value));
}
}


public string Describe(T value)
{
return this[value].Description;
}
}


[System.Diagnostics.DebuggerDisplay("{Value} ({Description})")]
public class EnumeratedValue<T>
{
private T value;
private string description;
internal EnumeratedValue(T value, string description) {
this.value = value;
this.description = description;
}
public T Value { get { return this.value; } }
public string Description { get { return this.description; } }
}


}

正如您可以看到的,这个集合很容易用 lambda 定制,以选择枚举器的子集和/或实现 string的自定义格式,而不是使用您提到的 GetDescription<T>(x)函数。

不要!枚举是基元而不是 UI 对象-使它们服务于。从设计的角度来看,ToString ()会非常糟糕。您试图在这里解决错误的问题: 真正的问题是您不希望 Enum.ToString ()出现在组合框中!

这是一个非常容易解决的问题,你可以创建一个 UI 对象来表示你的组合框项:

sealed class NicenessComboBoxItem
{
public string Description { get { return ...; } }
public HowNice Value { get; private set; }


public NicenessComboBoxItem(HowNice howNice) { Value = howNice; }
}

然后将这个类的实例添加到组合框的 Items 集合中,并设置以下属性:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";

ComboBox具有所需的一切: FormattingEnabled属性(应该将其设置为 true)和 Format事件(需要在其中放置所需的格式化逻辑)。因此,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
{
e.Value = GetDescription<HowNice>((HowNice)e.Value);
}

我认为,如果不简单地绑定到另一种类型,那么就无法完成这项工作——至少不方便。通常情况下,即使你不能控制 ToString(),你也可以使用 TypeConverter来进行自定义格式化——但是 IIRC 的 System.ComponentModel并不尊重枚举。

你可以绑定到描述的 string[],或者类似键/值对的东西?(描述/值)-类似于:

class EnumWrapper<T> where T : struct
{
private readonly T value;
public T Value { get { return value; } }
public EnumWrapper(T value) { this.value = value; }
public string Description { get { return GetDescription<T>(value); } }
public override string ToString() { return Description; }


public static EnumWrapper<T>[] GetValues()
{
T[] vals = (T[])Enum.GetValues(typeof(T));
return Array.ConvertAll(vals, v => new EnumWrapper<T>(v));
}
}

然后绑定到 EnumWrapper<HowNice>.GetValues()

您可以编写一个 TypeConverter 来读取指定的属性以便在资源中查找它们。因此,您可以毫不费力地获得对显示名称的多语言支持。

查看 TypeConverter 的 ConvertFrom/ConvertTo 方法,并使用反射来读取枚举 田野上的属性。

我将编写一个泛型类,用于任何类型:

public class ComboBoxItem<T>
{
/// The text to display.
private string text = "";
/// The associated tag.
private T tag = default(T);


public string Text
{
get
{
return text;
}
}


public T Tag
{
get
{
return tag;
}
}


public override string ToString()
{
return text;
}


// Add various constructors here to fit your needs
}

在此基础上,您可以添加一个静态的“工厂方法”来创建一个给定枚举类型的 combobox 项列表(与您的 GetDescription 方法非常相似)。这将节省您必须为每个枚举类型实现一个实体的时间,并且还为“ GetDescription”辅助方法提供了一个很好的/逻辑的位置(我个人将其称为 FromEnum (T obj) ..。

您可以创建一个泛型结构,用于具有描述的所有枚举。对于类与类之间的隐式转换,除了 ToString 方法之外,变量的工作方式仍然与枚举类似:

public struct Described<T> where T : struct {


private T _value;


public Described(T value) {
_value = value;
}


public override string ToString() {
string text = _value.ToString();
object[] attr =
typeof(T).GetField(text)
.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attr.Length == 1) {
text = ((DescriptionAttribute)attr[0]).Description;
}
return text;
}


public static implicit operator Described<T>(T value) {
return new Described<T>(value);
}


public static implicit operator T(Described<T> value) {
return value._value;
}


}

用法例子:

Described<HowNice> nice = HowNice.ReallyNice;


Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"

类型转换器。我想这就是我要找的。所有欢呼 西蒙 · 斯文森

[TypeConverter(typeof(EnumToStringUsingDescription))]
Enum HowNice {
[Description("Really Nice")]
ReallyNice,
[Description("Kinda Nice")]
SortOfNice,
[Description("Not Nice At All")]
NotNice
}

我需要在当前枚举中更改的是在它们的声明之前添加这一行。

[TypeConverter(typeof(EnumToStringUsingDescription))]

一旦我这样做,任何枚举将显示使用其字段的 DescriptionAttribute

对了,TypeConverter的定义是这样的:

public class EnumToStringUsingDescription : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType.Equals(typeof(Enum)));
}


public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType.Equals(typeof(String)));
}


public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return base.ConvertFrom(context, culture, value);
}


public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (!destinationType.Equals(typeof(String)))
{
throw new ArgumentException("Can only convert to string.", "destinationType");
}


if (!value.GetType().BaseType.Equals(typeof(Enum)))
{
throw new ArgumentException("Can only convert an instance of enum.", "value");
}


string name = value.ToString();
object[] attrs =
value.GetType().GetField(name).GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name;
}
}

这对我的 ComboBox 案例有所帮助,但显然不能真正覆盖 ToString()。我想我会同时满足于这个..。

您可以使用 PostSharp 来定位 Enum.ToString 并添加所需的 aditionall 代码。 这不需要任何代码更改。

您需要的是将枚举转换为 ReadonlyCollection 并将集合绑定到组合框(或任何启用了键值对的控件)

首先,您需要一个类来包含列表中的项。因为所有您需要的是 int/string 对,所以我建议使用一个接口和一个基类组合,这样您就可以在任何您想要的对象中实现功能:

public interface IValueDescritionItem
{
int Value { get; set;}
string Description { get; set;}
}


public class MyItem : IValueDescritionItem
{
HowNice _howNice;
string _description;


public MyItem()
{


}


public MyItem(HowNice howNice, string howNice_descr)
{
_howNice = howNice;
_description = howNice_descr;
}


public HowNice Niceness { get { return _howNice; } }
public String NicenessDescription { get { return _description; } }




#region IValueDescritionItem Members


int IValueDescritionItem.Value
{
get { return (int)_howNice; }
set { _howNice = (HowNice)value; }
}


string IValueDescritionItem.Description
{
get { return _description; }
set { _description = value; }
}


#endregion
}

下面是接口和实现它的示例类。注意,类的 Key 是 Enum 的强类型,而且 IValueDescritionItem 属性是显式实现的(因此类可以拥有任何属性,并且您可以选择实现 Key/Value 对的属性。

现在,EnumToReadOnlyCollection 类:

public class EnumToReadOnlyCollection<T,TEnum> : ReadOnlyCollection<T> where T: IValueDescritionItem,new() where TEnum : struct
{
Type _type;


public EnumToReadOnlyCollection() : base(new List<T>())
{
_type = typeof(TEnum);
if (_type.IsEnum)
{
FieldInfo[] fields = _type.GetFields();


foreach (FieldInfo enum_item in fields)
{
if (!enum_item.IsSpecialName)
{
T item = new T();
item.Value = (int)enum_item.GetValue(null);
item.Description = ((ItemDescription)enum_item.GetCustomAttributes(false)[0]).Description;
//above line should be replaced with proper code that gets the description attribute
Items.Add(item);
}
}
}
else
throw new Exception("Only enum types are supported.");
}


public T this[TEnum key]
{
get
{
return Items[Convert.ToInt32(key)];
}
}


}

因此,您在代码中需要的全部内容是:

private EnumToReadOnlyCollection<MyItem, HowNice> enumcol;
enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>();
comboBox1.ValueMember = "Niceness";
comboBox1.DisplayMember = "NicenessDescription";
comboBox1.DataSource = enumcol;

请记住,您的集合是使用 MyItem 键入的,因此如果您绑定到适当的属性,那么 combobox 值应该返回一个枚举值。

我添加了 T this [ Enum t ]属性,以使集合比简单的组合消费品更加有用,例如 textBox1.Text = 枚举[ HowNice ]。真的很好。描述;

当然,你可以选择将 MyItem 转换成一个只用于这个普通类的 Key/Value 类,有效地跳过了 EnumToReadnlyCollection 的类型参数中的 MyItem,但是之后你将不得不使用 int 来获取键(这意味着得到 combobox1.SelectedValue 将返回 int 而不是 enum 类型)。如果创建一个 KeyValueItem 类来替换 MyItem 等等,就可以解决这个问题。

您可以将 Enum 定义为

Enum HowNice {
[StringValue("Really Nice")]
ReallyNice,
[StringValue("Kinda Nice")]
SortOfNice,
[StringValue("Not Nice At All")]
NotNice
}

然后使用 HowNice.GetStringValue()

一旦您有了 GetDescription方法(它需要是全局静态的) ,您可以通过扩展方法使用它:

public static string ToString(this HowNice self)
{
return GetDescription<HowNice>(self);
}

抱歉把这个旧线头挂起来了。

我将按照下面的方法对枚举进行本地化,因为它可以通过下拉列表文本字段向用户显示有意义的本地化值,而不仅仅是描述。

首先,我创建了一个名为 OwToStringByCulture 的简单方法来从全局资源文件中获取本地化字符串,在本例中,它是 App _ GlobalResources 文件夹中的 BiBongNet.resx。在这个资源文件中,确保所有字符串都与枚举的值相同(ReallyNice、 SortOfnice、 NotNice)。在这个方法中,我传入了参数: resource ceClassName,它通常是资源文件的名称。

接下来,我创建一个静态方法,用枚举作为数据源填充下拉列表,称为 OwFillDataWithEnum。此方法可以在以后与任何枚举一起使用。

然后在下拉列表为 DropDownList1的页面中,我在 Page _ Load 中设置了以下一行简单的代码来填充下拉列表的枚举。

 BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1, "BiBongNet");

就是这样。我认为使用这样的一些简单方法,您可以用任何枚举填充任何列表控件,不仅可以显示描述性值,还可以显示本地化文本。您可以将所有这些方法作为扩展方法,以便更好地使用。

希望这个能帮上忙。 分享就是分享!

方法如下:

public class BiBongNet
{


enum HowNice
{
ReallyNice,
SortOfNice,
NotNice
}


/// <summary>
/// This method is for filling a listcontrol,
/// such as dropdownlist, listbox...
/// with an enum as the datasource.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ctrl"></param>
/// <param name="resourceClassName"></param>
public static void OwFillDataWithEnum<T>(ListControl ctrl, string resourceClassName)
{
var owType = typeof(T);
var values = Enum.GetValues(owType);
for (var i = 0; i < values.Length; i++)
{
//Localize this for displaying listcontrol's text field.
var text = OwToStringByCulture(resourceClassName, Enum.Parse(owType, values.GetValue(i).ToString()).ToString());
//This is for listcontrol's value field
var key = (Enum.Parse(owType, values.GetValue(i).ToString()));
//add values of enum to listcontrol.
ctrl.Items.Add(new ListItem(text, key.ToString()));
}
}


/// <summary>
/// Get localized strings.
/// </summary>
/// <param name="resourceClassName"></param>
/// <param name="resourceKey"></param>
/// <returns></returns>
public static string OwToStringByCulture(string resourceClassName, string resourceKey)
{
return (string)HttpContext.GetGlobalResourceObject(resourceClassName, resourceKey);
}
}
Enum HowNice {
[StringValue("Really Nice")]
ReallyNice,
[StringValue("Kinda Nice")]
SortOfNice,
[StringValue("Not Nice At All")]
NotNice
}


Status = ReallyNice.GetDescription()

使用您的枚举示例:

using System.ComponentModel;


Enum HowNice
{
[Description("Really Nice")]
ReallyNice,
[Description("Kinda Nice")]
SortOfNice,
[Description("Not Nice At All")]
NotNice
}

创建扩展:

public static class EnumExtensions
{
public static string Description(this Enum value)
{
var enumType = value.GetType();
var field = enumType.GetField(value.ToString());
var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute),
false);
return attributes.Length == 0
? value.ToString()
: ((DescriptionAttribute)attributes[0]).Description;
}
}

然后你可以使用下面这样的东西:

HowNice myEnum = HowNice.ReallyNice;
string myDesc = myEnum.Description();

更多信息参见: http://www.blackwasp.co.uk/EnumDescription.aspx。解决方案归功于理查德 · 卡尔

Enum HowNice {
[Description("Really Nice")]
ReallyNice,
[Description("Kinda Nice")]
SortOfNice,
[Description("Not Nice At All")]
NotNice
}

为了解决这个问题,你应该使用一个扩展方法和一个字符串数组,如下所示:

Enum HowNice {
ReallyNice  = 0,
SortOfNice  = 1,
NotNice     = 2
}


internal static class HowNiceIsThis
{
const String[] strings = { "Really Nice", "Kinda Nice", "Not Nice At All" }


public static String DecodeToString(this HowNice howNice)
{
return strings[(int)howNice];
}
}

简单代码和快速解码。

继@scraimer 的回答之后,下面是 enum-to-string 类型转换器的一个版本,它也支持标志:

    /// <summary>
/// A drop-in converter that returns the strings from
/// <see cref="System.ComponentModel.DescriptionAttribute"/>
/// of items in an enumaration when they are converted to a string,
/// like in ToString().
/// </summary>
public class EnumToStringUsingDescription : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType.Equals(typeof(Enum)));
}


public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType.Equals(typeof(String)));
}


public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return base.ConvertFrom(context, culture, value);
}


public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType.Equals(typeof(String)))
{
string name = value.ToString();
Type effectiveType = value.GetType();


if (name != null)
{
FieldInfo fi = effectiveType.GetField(name);
if (fi != null)
{
object[] attrs =
fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name;
}


}
}


return base.ConvertTo(context, culture, value, destinationType);
}


/// <summary>
/// Coverts an Enums to string by it's description. falls back to ToString.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public string EnumToString(Enum value)
{
//getting the actual values
List<Enum> values = EnumToStringUsingDescription.GetFlaggedValues(value);
//values.ToString();
//Will hold results for each value
List<string> results = new List<string>();
//getting the representing strings
foreach (Enum currValue in values)
{
string currresult = this.ConvertTo(null, null, currValue, typeof(String)).ToString();;
results.Add(currresult);
}


return String.Join("\n",results);


}


/// <summary>
/// All of the values of enumeration that are represented by specified value.
/// If it is not a flag, the value will be the only value retured
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
private static List<Enum> GetFlaggedValues(Enum value)
{
//checking if this string is a flaged Enum
Type enumType = value.GetType();
object[] attributes = enumType.GetCustomAttributes(true);
bool hasFlags = false;
foreach (object currAttibute in attributes)
{
if (enumType.GetCustomAttributes(true)[0] is System.FlagsAttribute)
{
hasFlags = true;
break;
}
}
//If it is a flag, add all fllaged values
List<Enum> values = new List<Enum>();
if (hasFlags)
{
Array allValues = Enum.GetValues(enumType);
foreach (Enum currValue in allValues)
{
if (value.HasFlag(currValue))
{
values.Add(currValue);
}
}






}
else//if not just add current value
{
values.Add(value);
}
return values;
}


}

以及使用它的扩展方法:

    /// <summary>
/// Converts an Enum to string by it's description. falls back to ToString
/// </summary>
/// <param name="enumVal">The enum val.</param>
/// <returns></returns>
public static string ToStringByDescription(this Enum enumVal)
{
EnumToStringUsingDescription inter = new EnumToStringUsingDescription();
string str = inter.EnumToString(enumVal);
return str;
}

我尝试过这种方法,它对我很有效。

我为枚举创建了一个包装类,并重载了隐式运算符,这样我就可以将它赋给枚举变量(在我的例子中,我必须将一个对象绑定到一个 ComboBox值)。

您可以使用反射来按照您希望的方式格式化枚举值,在我的例子中,我从枚举值中检索出 DisplayAttribute(如果存在的话)。

希望这个能帮上忙。

public sealed class EnumItem<T>
{
T value;


public override string ToString()
{
return Display;
}


public string Display { get; private set; }
public T Value { get; set; }


public EnumItem(T val)
{
value = val;
Type en = val.GetType();
MemberInfo res = en.GetMember(val.ToString())?.FirstOrDefault();
DisplayAttribute display = res.GetCustomAttribute<DisplayAttribute>();
Display = display != null ? String.Format(display.Name, val) : val.ToString();
}


public static implicit operator T(EnumItem<T> val)
{
return val.Value;
}


public static implicit operator EnumItem<T>(T val)
{
return new EnumItem<T>(val);
}
}

编辑:

为了以防万一,我使用以下函数来获取 ComboBoxDataSource所使用的 enum

public static class Utils
{
public static IEnumerable<EnumItem<T>> GetEnumValues<T>()
{
List<EnumItem<T>> result = new List<EnumItem<T>>();
foreach (T item in Enum.GetValues(typeof(T)))
{
result.Add(item);
}
return result;
}
}