如何绑定带有自定义字符串格式的枚举值的ComboBox?

140

在帖子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"

但是这并不能帮助我简单地使用枚举的值填充ComboBox,因为我无法强制ComboBox调用GetDescription

我想要实现以下要求:

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

显然,我可以为每个枚举实现一个新类,并覆盖其ToString()方法,但对于每个枚举来说这都是很多工作,我宁愿避免这种情况。

有任何想法吗?

嘿,我甚至会给你一个拥抱作为赏金 :-)


1
jjnguy说得对,Java枚举类型非常好地解决了这个问题(http://javahowto.blogspot.com/2006/10/custom-string-values-for-enum.html),但这个问题的相关性值得怀疑。 - Matthew Flaschen
8
Java的枚举类型有些可笑。或许他们会在2020年添加属性:/ - Chad Grant
对于一个更轻量级(但可能不够强大)的解决方案,请参见我的帖子 - Gutblender
21个回答

86

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

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

这只适用于数据绑定的组合框吗?否则我无法触发格式事件。 - SomethingBetter
这里唯一的问题是你无法按照自己的逻辑对列表进行排序。 - GorillaApe
这是一个很好的解决方案。不过我需要它能够与 DataGridComboBoxColumn 一起使用。解决的机会有多大?我找不到一种获取 DataGridComboBoxColumnComboBox 访问权限的方法。 - Soko

47

不要这样做!枚举是基本类型而不是 UI 对象——让它们在 ToString() 中为 UI 服务会从设计角度相当糟糕。你试图解决错误的问题:真正的问题是你不想让 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";

1
我完全同意。你也不应该将ToString()的结果暴露给用户界面。而且,你也无法进行本地化。 - Øyvind Skaar
1
我知道这很老,但有什么不同吗? - nportelli
3
我曾经看到过一个类似的解决方案,他们没有使用自定义类,而是将枚举值映射到一个Dictionary中,并使用KeyValue属性作为DisplayMemberValueMember - Jeff B

43

TypeConverter。我认为这就是我一直在寻找的东西。向Simon Svensson致敬!

[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;
    }
}

这对我的组合框案例有所帮助,但显然并没有真正覆盖 ToString()。我想我暂时就这么处理...


3
你正在处理枚举类型到字符串的转换,但如果你想要完整的实现,你还需要处理枚举类型到InstanceDescriptor的转换,以及字符串到枚举类型的转换。不过我猜目前只是展示这些内容已经足够了 ;) - sisve
1
这个解决方案只在你的描述是静态的时候有效,不幸的是。 - Llyle
3
我已经拼命尝试了几个小时,但甚至在简单的控制台应用程序中也无法实现。我使用 [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One} 装饰了枚举,尝试执行 Console.WriteLine(MyEnum.One),但结果仍然是"One"。你需要像 TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One) 那样的特殊方法吗?(这个方法可以正常工作) - Dav
@Dav - 你最终解决了吗?我也遇到了同样的问题。 - drzaus
1
@scraimer,我已经发布了支持标志的代码版本。版权归您所有... - Avi Turner
显示剩余2条评论

43

你可以编写一个TypeConverter,该转换器读取指定的属性并在资源中查找它们。因此,您可以轻松地获得多语言支持的显示名称而不需要太多麻烦。

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


好的,我写了一些代码(请参见我对这个问题的回答)- 你觉得这足够了吗?我有什么遗漏吗? - Shalom Craimer
1
不错。整体上更好,但对于普通的、永远不会全球化的软件来说可能有些过度了。(我知道,这个假设后来会被证明是错误的。;-)) - peSHIr

34

使用你的枚举示例:

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 以获取更多信息。感谢 Richrd Carr 提供的解决方案。


我按照引用网站上的细节进行了操作,使用如下方式,对我来说看起来很简单:'string myDesc = HowNice.ReallyNice.Description();'。myDesc 将输出“Really Nice”。 - Ananda

8
你可以创建一个通用的结构体,用于所有具有描述性的枚举。通过与类之间的隐式转换,你的变量仍然像枚举一样工作,只是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"

5
最好的方法是创建一个类。
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,而且没有人可以再创建该类的任何实例,这基本上使它成为一个枚举。

p.s. 可能需要进行一些细微的语法修复,我不是很擅长C#。(Java的人)


1
这怎么帮助解决下拉框问题? - peSHIr
1
我也会选择这个答案。 - Mikko Rantanen
3
看到原帖作者明确表示不想要一个类,我认为创建一个类并不会有太多的工作量。你可以将描述和ToString重载抽象到所有枚举的父类中。在此之后,你只需要一个构造函数private HowNice(String desc) : base(desc) { }和静态字段即可。 - Mikko Rantanen
每个枚举都需要自己的枚举类型。就像Mikko所说,您可以创建一个基本的EnumToString类,并通过简单地扩展它来完成所有工作。 - jjnguy
哦。顺便提一下,你不能初始化一个非字符串引用常量,所以 C# 语法让 jjnguy 受到了打击。正确的定义是 public readonly static 而不是 public const - Mikko Rantanen
显示剩余4条评论

5

我认为你不能方便地做到不绑定到不同类型。通常,即使您无法控制 ToString(),也可以使用 TypeConverter 进行自定义格式化 - 但是我记得对于枚举,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));
    }
}

And then bind to EnumWrapper<HowNice>.GetValues()


1
当前上下文中不存在名称为'GetDescription'的内容。我正在使用.NET 4.0。 - Muhammad Adeel Zahid
@MuhammadAdeelZahid 仔细看一下问题的开头 - 这来自链接帖子:https://dev59.com/knRB5IYBdhLWcg3w4bEo - Marc Gravell
抱歉,无法从问题中获取任何线索。您的方法未编译并显示错误。 - Muhammad Adeel Zahid
嗨,马克,我试了你的想法。它有效,但是theComboBox.SelectItemEnumWrapper<T>类型,而不是T本身。我认为scraimer想要Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value. - Peter Lee

4

在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)

这不是一个很好的解决方案,用了switch语句等等 - 但它应该可以工作,希望不需要太多的重写...

请注意,绑定到枚举的控件不会调用此扩展方法,而是调用默认实现。 - Richard Szalay
好的。所以如果你需要某个地方的描述,这是一个可行的选择,但它不能解决提出的组合框问题。 - peSHIr
更大的问题是这个方法永远不会被调用(作为扩展方法)- 已经存在的实例方法总是优先。 - Marc Gravell
当然,Marc是对的(像往常一样?)。我的.NET经验很少,但向方法提供一个虚拟参数应该可以解决问题。编辑后的答案。 - Björn
为了填充组合框项目,一旦我们有了这个扩展方法,我们可以编写以下代码:foreach (HowNice item in (HowNice[])Enum.GetValues(typeof(HowNice))) { myComboBox.Items.Add(item.ToString(0)); } - RBT

3

考虑到您不想为每个枚举创建一个类,我建议创建一个枚举值/显示文本的字典,并绑定该字典。

请注意,这取决于原帖中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;
}

所选项的Key将是实际的枚举值。此外,不要使描述字符串发生冲突 - 用户如何区分它们? - Richard Szalay
如果您有冲突的描述字符串,那么您不应该直接将枚举的值绑定到组合框。 - Richard Szalay
1
ComboBox.DataSource = 字典; - Richard Szalay
你能做到那个?该死!太棒了! - Shalom Craimer
抱歉,我知道这很老了,但是...在 ComboBox.DataSource=dictionary; 之后,还需要添加 ComboBox.DisplayMember="Key";ComboBox.ValueMember="Value"; - nurchi
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接