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


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 и используйте отражение, чтобы прочитать атрибуты в ваших полях перечисления .


Хорошо, я написал некоторый код (см. Мой ответ на этот вопрос) - вы думаете, этого достаточно, я что-то упустил?
— Шалом Креймер

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"

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


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)функции, которую вы упоминаете.


Отлично, но я ищу что-то, что требует еще меньше работы в коде.
— Шалом Креймер

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

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

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

3
Добро пожаловать в stackoverflow! Всегда лучше предоставить краткое описание примера кода, чтобы повысить точность публикации :)
— Picrofo Software

-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.