Як я маю комбіновану коду, пов’язану із перерахуванням, із спеціальним форматуванням рядків для значень enum?


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 значеннями enum, оскільки я не можу змусити ComboBox викликатиGetDescription .

Те, що я хочу, має такі вимоги:

  • Читання (HowNice)myComboBox.selectedItemповерне вибране значення як значення перерахунку.
  • Користувач повинен бачити зручні для відображення рядки відображення, а не лише назву значень перерахування. Отже, замість того, щоб бачити " NotNice", користувач побачив би " Not Nice At All".
  • Сподіваємось, що рішення потребує мінімальних змін коду до існуючих перерахувань.

Очевидно, я міг би впровадити новий клас для кожного переліку, який я створюю, і перекрити його ToString(), але це багато роботи для кожного перерахунку, і я вважаю за краще цього уникати.

Будь-які ідеї?

Чорт забираю, я навіть кину в обійми як щедрість :-)


1
jjnguy вірно, що Java-переслідувачі вирішують це добре ( javahowto.blogspot.com/2006/10/… ), але це сумнівно.
Меттью Флашен

8
Java Enums - жарт. Можливо, вони додадуть "Властивості" у 2020 році: /
Чад Грант

Щодо легшого (але, мабуть, менш надійного) рішення, дивіться мою нитку .
Гутблендер

Відповіді:


42

Ви можете написати TypeConverter, який читає визначені атрибути, щоб переглянути їх у своїх ресурсах. Таким чином, ви отримаєте багатомовну підтримку відображуваних імен без особливих клопотів.

Подивіться на методи ConvertFrom / ConvertTo TypeConverter та використовуйте відображення для читання атрибутів у полях перерахування .


Гаразд, я написав якийсь код (див. Мою відповідь на це запитання) - ви вважаєте, що цього достатньо, я щось пропускаю?
Шалом Креймер

1
Хороший. Краще просто, але це може бути зайвим для вашої запущеної млини, яка ніколи не буде глобалізованою в будь-який спосіб. (Я знаю, що це припущення згодом виявиться помилковим. ;-))
peSHIr

85

ComboBox має все необхідне: the FormattingEnabled властивість, яку слід встановити true, і Formatподію, де потрібно розмістити потрібну логіку форматування. Таким чином,

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

Це працює тільки з комбінованими даними? Я не можу змусити подія "Формати" запустити інше.
ЩосьБільше

Єдина проблема тут полягає в тому, що ти не
можеш зіставити

Це чудове рішення. Мені це знадобиться для роботи DataGridComboBoxColumn. Будь-який шанс вирішити це? Я не можу знайти спосіб , щоб отримати доступ до ComboBoxз DataGridComboBoxColumn.
Соко

46

Не треба! Енуми - це примітиви, а не об’єкти інтерфейсу користувача - змусити їх обслуговувати інтерфейс у .ToString () з точки зору дизайну було б зовсім погано. Тут ви намагаєтеся вирішити неправильну проблему: справжня проблема полягає в тому, що ви не хочете, щоб Enum.ToString () відображався у вікні комбінації!

Тепер це справді дуже вирішувана проблема! Ви створюєте об’єкт інтерфейсу, щоб представляти елементи комбінованого вікна:

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

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

А потім просто додайте екземпляри цього класу до колекції елементів об’єднаного вікна та встановіть ці властивості:

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

1
Я погоджуюся від усієї душі. Ви також не повинні піддавати результат інтерфейсу користувача ToString (). І ви не отримаєте локалізацію.
Øyvind Skaar

Я знаю, що це по-старому, але чим це відрізняється?
nportelli

2
Я бачив подібне рішення, де замість користувальницького класу вони зіставляли значення enum до a Dictionaryта використовували властивості Keyта 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 не пов'язане зі статичними описами, прикривач може заповнювати значення з інших джерел, ніж атрибути.
Дмитро Ташкінов

3
Мене вже кілька годин тягнуло за волоссям, але це все одно не працює навіть у простих консольних програмах. Я прикрасив ентум [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, спробував це зробити, Console.WriteLine(MyEnum.One)і це все ще виходить як "Один". Вам потрібна якась особлива магія на кшталт TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(яка добре працює)?
Дав

1
@scraimer Я опублікував версію вашого коду, що підтримує прапори. всі права захищені для вас ...
Avi Turner

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 виведе дуже добре
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

Я не думаю, що ти можеш це зробити, не просто прив’язавшись до іншого типу - принаймні, не зручно. Зазвичай, навіть якщо ви не можете контролювати ToString(), ви можете використовувати TypeConverterспеціальне форматування, але IIRCSystem.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
Марк Gravell

вибачте, але не можу отримати жодного поняття з питання. ваш метод не компілюється і показує помилку.
Мухаммед Адель Захід Задід

Привіт Марку, я спробував твою ідею. Це працює, але 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)


1
Як це допомагає у поєднанні з проблемою комбобокса?
peSHIr

Ну, а тепер, коли новий об’єкт буде розміщено у комбобоксі, його ToString буде належним чином відображений, а клас все ще діє як перерахунок.
jjnguy

1
Був би і моєю відповіддю.
Мікко Рантанен

3
І бачити, як оригінальний плакат явно не хотів класу. Я не думаю, що клас - це набагато більше роботи. Ви можете абстрагувати опис і ToString замінити батьківський клас для всіх переліків. Після цього все, що вам потрібно, - це конструктор private HowNice(String desc) : base(desc) { }та статичні поля.
Мікко Рантанен

Я сподівався цього уникнути, оскільки це означає, що для кожного перерахунку я потребую свого класу. Тьфу.
Шалом Креймер

3

Зважаючи на те, що ви краще не створюєте клас для кожного перерахунку, я рекомендую створити словник значення enum / відображати текст та прив’язати його.

Зауважте, що це залежить від методів методу 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;
}

Хороша ідея. Але як я можу це використовувати за допомогою комбобокса? Як тільки користувач вибирає елементи з комбінованої скриньки, як я можу знати, який із елементів він вибрав? Пошук за рядком Опис? Це змушує мою шкіру свербіти ... (між рядками Опис може виникнути рядок "зіткнення")
Шалом Креймер,

Ключ вибраного елемента буде фактичним значенням перерахунку. Крім того, не стикайтеся з рядками опису - як користувач скаже різницю?
Річард Салай

<cont> якщо у вас є рядки опису, які стикаються, ви не повинні прив'язувати значення перерахунку до комбобоксу в будь-якому випадку.
Річард Салай

хммм ... Ну, чи не могли б ви надати приклад коду про те, як ви додавали елементи до комбінованої скриньки? Все, що я можу придумати, - це "foreach (рядок s в описахDict.Values) {this.combobox.Items.Add (s);}"
Шалом Креймер,

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)

Це не дуже приємне рішення, із заявою про перемикання та все - але воно повинно працювати і, сподіваємось, без багатьох переписувачів ...


Майте на увазі, що елемент керування, який пов'язується з вашою перерахунком, не називатиме цей метод розширення, а закликає реалізацію за замовчуванням.
Річард Салай

Правильно. Тож це життєздатний варіант, якщо вам потрібен опис десь, це не допоможе з поставленою проблемою комбобоксу.
peSHIr

Більша проблема полягає в тому, що це ніколи не буде викликано (як метод розширення) - пріоритетні методи екземплярів завжди мають пріоритет.
Марк Гравелл

Звичайно, Марк має рацію (як завжди?). Мій досвід .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
}

Крім цього, ви можете додати статичний "фабричний метод", щоб створити список елементів комбінації з урахуванням типу enum (майже такий же, як і метод GetDescriptions, який у вас є). Це позбавить вас від необхідності впроваджувати по одній суті для кожного типу перерахунку, а також надасть гарне / логічне місце для допоміжного методу "GetDescriptions" (особисто я би назвав це 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 та додавання додаткового коду, який потрібно. Для цього не потрібні зміни коду.


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 цього [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);
        }
}

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

Простий код і швидке декодування.


змінна строка повинна бути статичною і оголошуватися так: Static String [] strings = new [] {...}
Sérgio

Єдина проблема з цим полягає в тому, що вам потрібно буде мати функцію для кожного перерахування, а опис буде окремо від самого перерахування ...
Аві Тернер

1

Я спробував такий підхід, і він спрацював на мене.

Я створив клас обгортки для перерахунків і перевантажив неявний оператор, щоб я міг призначити його перерахуванням змінних (у моєму випадку я повинен був прив’язати об'єкт до ComboBoxзначення).

Ви можете використовувати відображення для форматування значень enum так, як ви хочете, у моєму випадку я DisplayAttributeвитягую із значень enum (якщо вони існують).

Сподіваюся, це допомагає.

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

3
Ласкаво просимо в stackoverflow! Завжди краще надати короткий опис зразкового коду для поліпшення точності публікації :)
Програмне забезпечення Picrofo

-1

Ви можете визначити Enum як

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

а потім використовувати HowNice.GetStringValue().


2
Це не компілюється (у мене є .NET 3.5). Де оголошено ´StringValue´?
трепет

1
Відповідь від @scraimer така ж, за винятком того, що він використовує атрибут поза рамками, тоді як ви використовуєте якийсь самовизначений атрибут.
Олівер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.