Как получить комбинированный список с привязкой перечисления с пользовательским форматированием строки для значений перечисления? - PullRequest
132 голосов
/ 28 апреля 2009

В посте 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 вернет выбранное значение в качестве значения перечисления.
  • Пользователь должен видеть удобные для пользователя строки отображения, а не только имя значений перечисления. Поэтому вместо "NotNice" пользователь увидит "Not Nice At All".
  • Надеемся, что решение потребует минимальных изменений кода в существующих перечислениях.

Очевидно, я мог бы реализовать новый класс для каждого создаваемого перечисления и переопределить его ToString(), но для каждого перечисления это большая работа, и я бы предпочел этого избежать.

Есть идеи?

Черт, я даже добавлю объятия в качестве награды: -)

Ответы [ 21 ]

85 голосов
/ 28 апреля 2009

ComboBox имеет все, что вам нужно: свойство FormattingEnabled, которое вы должны установить на true, и событие Format, где вам нужно будет разместить желаемую логику форматирования. Таким образом,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
44 голосов
/ 28 апреля 2009

Не! Перечисления являются примитивами, а не объектами пользовательского интерфейса - заставить их обслуживать пользовательский интерфейс в .ToString () было бы очень плохо с точки зрения дизайна. Вы пытаетесь решить не ту проблему здесь: настоящая проблема в том, что вы не хотите, чтобы Enum.ToString () отображался в поле со списком!

Теперь это действительно решаемая проблема! Вы создаете объект пользовательского интерфейса для представления элементов вашего поля со списком:

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";
41 голосов
/ 28 апреля 2009

Вы можете написать TypeConverter, который читает указанные атрибуты, чтобы искать их в ваших ресурсах. Таким образом, вы получите многоязычную поддержку отображаемых имен без особых хлопот.

Просмотрите методы ConvertFrom / ConvertTo TypeConverter и используйте отражение, чтобы прочитать атрибуты в вашем перечислении поля .

41 голосов
/ 28 апреля 2009

TypeConverter. Я думаю, что это то, что я искал. Всем привет Саймон Свенссон !

[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(). Я полагаю, что я согласен на это ...

31 голосов
/ 01 августа 2012

Используя ваш пример перечисления:

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 для получения дополнительной информации. Кредит идет Ричдру Карру за решение

8 голосов
/ 28 апреля 2009

Вы можете создать общую структуру, которую вы можете использовать для всех ваших перечислений, которые имеют описания. При неявных преобразованиях в класс и из него ваши переменные по-прежнему работают как перечисление, за исключением метода 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 голосов
/ 28 апреля 2009

Я не думаю, что вы можете сделать это без простой привязки к другому типу - по крайней мере, не удобно. Обычно, даже если вы не можете контролировать 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()

5 голосов
/ 28 апреля 2009

Лучший способ сделать это - создать класс.

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 парень)

3 голосов
/ 28 апреля 2009

Учитывая, что вы не хотите создавать класс для каждого перечисления, я бы рекомендовал создать словарь значения перечисления / отображаемого текста и связать его вместо этого.

Обратите внимание, что это зависит от методов метода 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;
}
3 голосов
/ 28 апреля 2009

Невозможно переопределить ToString () перечислений в C #. Однако вы можете использовать методы расширения;

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

Конечно, вам придется сделать явный вызов метода, т. Е.

HowNice.ReallyNice.ToString(0)

Это не очень хорошее решение, с оператором switch и всем - но оно должно работать и, надеюсь, без переписывания ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...