Як я маю комбіновану коду, пов’язану із перерахуванням, із спеціальним форматуванням рядків для значень 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.