C# 如何使用枚举值的自定义字符串格式设置枚举绑定组合框?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/796607/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 01:32:11  来源:igfitidea点击:

How do I have an enum bound combobox with custom string formatting for enum values?

c#comboboxenums

提问by Shalom Craimer

In the post Enum ToString, a method is described to use the custom attribute DescriptionAttributelike this:

Enum ToString帖子中,描述了一种使用自定义属性的方法,DescriptionAttribute如下所示:

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

And then, you call a function GetDescription, using syntax like:

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

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

But that doesn't really help me when I want to simply populate a ComboBox with the values of an enum, since I cannot force the ComboBox to call GetDescription.

但是当我想简单地用枚举的值填充 ComboBox 时GetDescription,这并没有真正帮助我,因为我不能强制 ComboBox 调用.

What I want has the following requirements:

我想要的有以下要求:

  • Reading (HowNice)myComboBox.selectedItemwill return the selected value as the enum value.
  • The user should see the user-friendly display strings, and not just the name of the enumeration values. So instead of seeing "NotNice", the user would see "Not Nice At All".
  • Hopefully, the solution will require minimal code changes to existing enumerations.
  • 读取(HowNice)myComboBox.selectedItem将返回选定的值作为枚举值。
  • 用户应该看到用户友好的显示字符串,而不仅仅是枚举值的名称。因此NotNice,用户看到的不是“ ”,而是“ Not Nice At All”。
  • 希望该解决方案需要对现有枚举进行最少的代码更改。

Obviously, I could implement a new class for each enum that I create, and override its ToString(), but that's a lot of work for each enum, and I'd rather avoid that.

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

Any ideas?

有任何想法吗?

Heck, I'll even throw in a hugas a bounty :-)

哎呀,我什至会拥抱作为赏金:-)

采纳答案by sisve

You could write an TypeConverter that reads specified attributes to look them up in your resources. Thus you would get multi-language support for display names without much hassle.

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

Look into the TypeConverter's ConvertFrom/ConvertTo methods, and use reflection to read attributes on your enum fields.

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

回答by jjnguy

The best way to do this is to make a class.

最好的方法是创建一个类。

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!");
}

I believe that is the best way to do it.

我相信这是最好的方法。

When stuffed in comboboxes the pretty ToString will be shown, and the fact that no one can make any more instances of your class essentially makes it an enum.

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

p.s. there may need to be some slight syntax fixes, I'm not super good with C#. (Java guy)

ps 可能需要一些轻微的语法修复,我对 C# 不是很好。(爪哇人)

回答by Bj?rn

Not possible to override the ToString() of enums in C#. However, you can use extension methods;

无法覆盖 C# 中枚举的 ToString()。但是,您可以使用扩展方法;

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

Of course you will have to make an explicit call to the method, i.e;

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

HowNice.ReallyNice.ToString(0)

This is not a nice solution, with a switch statement and all - but it should work and hopefully whitout to many rewrites...

这不是一个很好的解决方案,有一个 switch 语句和所有 - 但它应该可以工作,并且希望没有许多重写......

回答by Richard Szalay

Given that you'd rather not create a class for each enum, I'd recommend creating a dictionary of the enum value/display text and binding that instead.

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

Note that this has a dependency on the GetDescription method methods in the original post.

请注意,这依赖于原始帖子中的 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;
}

回答by peSHIr

Create a collection that contains what you need (like simple objects containing a Valueproperty containing the HowNiceenum value and a Descriptionproperty containing GetDescription<HowNice>(Value)and databind the combo to that collection.

创建一个包含您需要的集合(例如包含Value包含HowNice枚举值的Description属性GetDescription<HowNice>(Value)和包含组合并将组合数据绑定到该集合的属性的简单对象。

Bit like this:

有点像这样:

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

when you have a collection class like this:

当你有一个这样的集合类时:

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

}

As you can see, this collection is easily customizable with lambda's to select a subset of your enumerator and/or implement a custom formatting to stringinstead of using the GetDescription<T>(x)function you mention.

如您所见,此集合可以使用 lambda 轻松自定义,以选择枚举器的子集和/或实现自定义格式,string而不是使用GetDescription<T>(x)您提到的函数。

回答by Sander

Don't! Enums are primitives and not UI objects - making them serve the UI in .ToString() would be quite bad from a design standpoint. You are trying to solve the wrong problem here: the real issue is that you do not want Enum.ToString() to show up in the combo box!

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

Now this is a very solveable problem indeed! You create a UI object to represent your combo box items:

现在这确实是一个很好解决的问题!您创建一个 UI 对象来表示您的组合框项目:

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

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

And then just add instances of this class to your combo box's Items collection and set these properties:

然后只需将此类的实例添加到组合框的 Items 集合并设置以下属性:

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

回答by Anton Gogolev

ComboBoxhas everything you need: the FormattingEnabledproperty, which you should set to true, and Formatevent, where you'll need to place desired formatting logic. Thus,

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

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

回答by Marc Gravell

I don't think you can do it without simply binding to a different type - at least, not conveniently. Normally, even if you can't control ToString(), you can use a TypeConverterto do custom formatting - but IIRC the System.ComponentModelstuff doesn't respect this for enums.

我认为如果不简单地绑定到不同的类型就无法做到这一点 - 至少,不方便。通常,即使您无法控制ToString(),您也可以使用 aTypeConverter进行自定义格式设置 - 但 IIRC 的System.ComponentModel东西不尊重枚举的这一点。

You could bind to a string[]of the descriptions, or a something essentially like a key/value pair? (desription/value) - something like:

您可以绑定到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()

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

回答by Dan C.

I would write a generic class for use with any type. I've used something like this in the past:

我会编写一个用于任何类型的通用类。我过去使用过这样的东西:

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
}

On top of this, you could add a static "factory method" to create a list of combobox items given an enum type (pretty much the same as the GetDescriptions method you have there). This would save you of having to implement one entity per each enum type, and also provide a nice/logical place for the "GetDescriptions" helper method (personally I would call it FromEnum(T obj) ...

最重要的是,您可以添加一个静态“工厂方法”来创建给定枚举类型的组合框项目列表(与您在那里拥有的 GetDescriptions 方法几乎相同)。这将使您不必为每个枚举类型实现一个实体,并且还为“GetDescriptions”辅助方法提供了一个不错的/合乎逻辑的位置(我个人将其称为 FromEnum(T obj) ...

回答by Guffa

You could make a generic struct that you could use for all of your enums that has descriptions. With implicit conversions to and from the class, your variables still works like the enum except for the ToString method:

您可以创建一个通用结构,可用于所有具有描述的枚举。通过与类之间的隐式转换,您的变量仍然像枚举一样工作,除了 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;
    }

}

Usage example:

用法示例:

Described<HowNice> nice = HowNice.ReallyNice;

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