Как получить комбинированный список с привязкой перечисления с пользовательским форматированием строки для значений перечисления?

135

В посте 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(), но для каждого перечисления это большая работа, и я бы предпочел этого избежать.

Любые идеи?

Черт, я даже бросить в объятия как Баунти :-)

Шалом Креймер
источник
1
jjnguy верно, что перечисления Java решают эту проблему хорошо ( javahowto.blogspot.com/2006/10/… ), но это имеет сомнительное значение.
Мэтью Флэшен
8
Java Enums - это шутка. Может быть, они добавят свойства в 2020 году: /
Чад Грант
Более легкое (но, возможно, менее надежное) решение смотрите в моей ветке .
Gutblender

Ответы:

42

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

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

sisve
источник
Хорошо, я написал некоторый код (см. Мой ответ на этот вопрос) - вы думаете, этого достаточно, я что-то упустил?
Шалом Креймер
1
Хороший. Лучше всего, но это может быть излишним для вашей заурядной программы, которая никогда не будет глобализирована в любом случае. (Я знаю, что это предположение позже окажется ложным. ;-))
peSHIr
85

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

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Антон Гоголев
источник
Это работает только с комбинированными списками данных? Я не могу заставить событие Format запускаться иначе.
SomethingBetter
единственная проблема здесь в том, что вы не можете отсортировать список по вашей логике
GorillaApe
Это отличное решение. Мне нужно было бы работать с DataGridComboBoxColumnхотя. Есть ли шанс решить это? Я не могу найти способ , чтобы получить доступ к ComboBoxиз DataGridComboBoxColumn.
Soko
46

Не надо! Перечисления являются примитивами, а не объектами пользовательского интерфейса - заставить их обслуживать пользовательский интерфейс в .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";
шлифовальный
источник
1
Я согласен от всего сердца. Также вы не должны предоставлять результат ToString () для пользовательского интерфейса. И вы не получаете локализацию.
Ойвинд Скаар
Я знаю, что это старый, но как это отличается?
nportelli
2
Я видел похожее решение, где вместо использования пользовательского класса они отображали значения перечисления в a Dictionaryи использовали свойства Keyand Valueв качестве DisplayMemberи ValueMember.
Джефф Б
42

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

Шалом Креймер
источник
3
Вы обрабатываете Enum -> String, но вам также понадобятся Enum> InstanceDescriptor и String -> Enum, если вы хотите завершить реализацию. Но я полагаю, что пока это достаточно для ваших нужд. ;)
sisve
1
Это решение работает, только когда ваши описания статичны, к сожалению.
Llyle
Кстати, использование TypeConverter не связано со статическим дескрипциями, так как coverter может заполнять значения из других источников, кроме атрибутов.
Дмитрий Ташкинов
3
Уже несколько часов тяну меня за волосы, но, похоже, это не работает даже в простых консольных приложениях. Я украсил enum [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, попытался сделать, Console.WriteLine(MyEnum.One)и это все еще выходит как "Один". Вам нужна особая магия вроде TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(которая работает нормально)?
Дав
1
@scraimer Я разместил версию вашего кода с поддержкой флагов. Все права защищены для вас ...
Ави Тёрнер
32

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

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

Тайлер Дурден
источник
Я проследил подробности на указанном сайте и использовал его следующим образом: для меня это выглядит просто 'string myDesc = HowNice.ReallyNice.Description ();' myDesc выведет действительно хороший
Ананда
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"
Guffa
источник
5

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

Марк Гравелл
источник
1
Имя «GetDescription» не существует в текущем контексте. я использую .NET 4.0
Мухаммед Адель Захид
@MuhammadAdeelZahid внимательно посмотрите на начало вопроса - это связано с постом: stackoverflow.com/questions/479410/enum-tostring
Марк Гравелл
извините, но не могу получить никакой подсказки от вопроса. ваш метод не компилируется и показывает ошибку.
Мухаммед Адель Захид
Привет Марк, я попробовал твою идею. Это работает, но theComboBox.SelectItemэто тип EnumWrapper<T>, а не Tсам по себе. Я думаю скреймер хочет Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
Питер Ли
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, и тот факт, что никто не может создавать больше экземпляров вашего класса, по существу делает его перечислением.

PS, возможно, должны быть некоторые небольшие исправления синтаксиса, я не очень хорош с C #. (Java парень)

jjnguy
источник
1
Как это помогает с проблемой со списком?
ПЕШИР
Что ж, теперь, когда новый объект помещается в комбинированный список, его ToString будет отображаться правильно, и класс по-прежнему будет действовать как перечисление.
jjnguy
1
Был бы и мой ответ.
Микко Рантанен
3
И увидев, как оригинальный постер явно не хотел занятия. Я не думаю, что класс - это намного больше работы. Вы можете абстрагировать описание и ToString переопределить родительский класс для всех перечислений. После этого все, что вам нужно, это конструктор private HowNice(String desc) : base(desc) { }и статические поля.
Микко Рантанен
Я надеялся избежать этого, поскольку это означает, что каждое перечисление, которое я делаю, потребует своего собственного класса. Тьфу.
Шалом Креймер
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;
}
Ричард Сзалай
источник
Хорошая идея. Но как бы я использовал это со списком? Как только пользователь выбирает элементы в выпадающем списке, как узнать, какой из элементов он выбрал? Поиск по строке описания? Это заставляет мою кожу чесаться ... (между строками описания может быть строка "столкновение")
Shalom Craimer
Ключ выбранного элемента будет фактическим значением перечисления. Кроме того, не сталкивайтесь со строками описания - как пользователь скажет разницу?
Ричард Сзалай
<cont> если у вас есть строки описания, которые сталкиваются, то вы не должны связывать значения перечисления напрямую со списком.
Ричард Сзалай
хммм ... Ну, не могли бы вы привести пример кода о том, как вы добавляете элементы в выпадающий список? Все, что я могу думать, это «foreach (строка s в описаниях
Dict.Values
1
ComboBox.DataSource = словарь;
Ричард Сзалай
3

Невозможно переопределить 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 и прочим - но оно должно работать и, надеюсь, без переписывания ...

Бьерн
источник
Имейте в виду, что элемент управления, связанный с вашим перечислением, не будет вызывать этот метод расширения, он вызовет реализацию по умолчанию.
Ричард Сзалай
Правильно. Так что это жизнеспособный вариант, если вам нужно где-то описание, это не поможет с поставленной проблемой выпадающего списка.
ПЕШИР
Большая проблема в том, что это никогда не будет вызвано (как метод расширения) - методы, которые уже существуют, всегда имеют приоритет.
Марк Гравелл
Конечно, Марк прав (как всегда?). Мой опыт работы с .NET минимален, но предоставление фиктивного параметра методу должно помочь. Отредактированный ответ.
Бьорн,
2

В продолжение ответа @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;
    }
Ави Тернер
источник
1

Я бы написал общий класс для использования с любым типом. Я использовал что-то подобное в прошлом:

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
}

Кроме того, вы можете добавить статический «фабричный метод», чтобы создать список элементов комбинированного списка с заданным типом перечисления (почти такой же, как у вашего метода GetDescription). Это избавит вас от необходимости реализовывать одну сущность для каждого типа перечисления, а также обеспечит удобное / логичное место для вспомогательного метода «GetDescription» (лично я бы назвал его FromEnum (T obj) ...

Дэн С.
источник
1

Создайте коллекцию, которая содержит то, что вам нужно (например, простые объекты, содержащие Valueсвойство, содержащее HowNiceзначение enum, и Descriptionсвойство, содержащее GetDescription<HowNice>(Value)и привязывающее комбинацию данных к этой коллекции.

Немного похоже на это:

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

}

Как вы можете видеть, эту коллекцию легко настроить с помощью лямбды, чтобы выбрать подмножество вашего перечислителя и / или реализовать пользовательское форматирование stringвместо использования GetDescription<T>(x)функции, которую вы упоминаете.

peSHIr
источник
Отлично, но я ищу что-то, что требует еще меньше работы в коде.
Шалом Креймер
Даже если вы можете использовать одну и ту же универсальную коллекцию для такого рода вещей для всех ваших счетчиков? Конечно, я не предлагал писать такую ​​коллекцию для каждого перечисления.
ПЕШИР
1

Вы можете использовать PostSharp для таргетинга Enum.ToString и добавить дополнительный код, который вы хотите. Это не требует каких-либо изменений кода.

majkinetor
источник
1

Вам нужно превратить перечисление в коллекцию 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
}

Вот интерфейс и пример класса, который его реализует. Обратите внимание, что ключ класса строго типизирован для Enum, и что свойства IValueDescritionItem реализованы явно (поэтому класс может иметь любые свойства, и вы можете выбрать те, которые реализуют Пара ключ / значение.

Теперь класс 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, поэтому значение в выпадающем списке должно возвращать значение перечисления, если вы привязываетесь к соответствующему объекту.

Я добавил свойство T this [Enum t], чтобы сделать коллекцию еще более полезной, чем простой комбинированный расходный материал, например textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

Конечно, вы можете выбрать превращение MyItem в класс Key / Value, используемый только для этой куколки, фактически пропуская MyItem в аргументах типа EnumToReadnlyCollection, но тогда вам придется использовать для ключа int (что означает получение combobox1.SelectedValue). вернул бы int, а не тип enum). Вы можете обойти это, если создадите класс KeyValueItem для замены MyItem и так далее, и так далее ...


источник
1

Извините за получение этой старой темы.

Я бы пошел следующим образом, чтобы локализовать enum, так как он может отображать значимые и локализованные значения для пользователя, а не только описание, через текстовое поле раскрывающегося списка в этом примере.

Сначала я создаю простой метод с именем OwToStringByCulture для получения локализованных строк из файла глобальных ресурсов, в данном примере это BiBongNet.resx в папке App_GlobalResources. Внутри этого файла ресурсов убедитесь, что все строки совпадают со значениями перечисления (ReallyNice, SortOfNice, NotNice). В этом методе я передаю параметр: resourceClassName, который обычно является именем файла ресурса.

Затем я создаю статический метод для заполнения выпадающего списка enum в качестве источника данных, который называется 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);
        }
}
BiBongNet
источник
1
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];
 }
}

Простой код и быстрое декодирование.

Сержио
источник
переменная strings должна быть статической и должна быть объявлена ​​следующим образом: Static String [] strings = new [] {...}
Сержио
Единственная проблема с этим заключается в том, что вам нужно будет иметь функцию для каждого перечисления, а описание будет отделено от самого перечисления ...
Ави Тернер
1

Я попробовал этот подход, и он работал для меня.

Я создал класс-обертку для перечислений и перегрузил неявный оператор, чтобы я мог назначить его переменным перечисления (в моем случае мне пришлось привязать объект к 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);
    }
}

РЕДАКТИРОВАТЬ:

На всяком случае, я использую следующую функцию , чтобы получить enumзначение , которые я использую для DataSourceизComboBox

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;
    }
}
Иезекииль Монеро Томпсон
источник
0

Когда у вас есть GetDescriptionметод (он должен быть глобальным статическим), вы можете использовать его через метод расширения:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}
трепет
источник
0
Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
}

Status = ReallyNice.GetDescription()
user1308805
источник
3
Добро пожаловать в stackoverflow! Всегда лучше предоставить краткое описание примера кода, чтобы повысить точность публикации :)
Picrofo Software
-1

Вы можете определить Enum как

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

а затем использовать HowNice.GetStringValue().

Vrushal
источник
2
Это не компилируется (у меня .NET 3.5). Где объявлено «StringValue»?
благоговение
1
Ответ от @scraimer тот же, за исключением того, что он использует атрибут вне фреймворка, тогда как вы используете какой-то определенный пользователем атрибут.
Оливер