Como eu tenho uma caixa de combinação vinculada a enum com formatação de string personalizada para valores de enum?

135

No post Enum ToString , é descrito um método para usar o atributo personalizado DescriptionAttributecomo este:

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

E então, você chama uma função GetDescription, usando sintaxe como:

GetDescription<HowNice>(NotNice); // Returns "Not Nice At All"

Mas isso realmente não me ajuda quando quero simplesmente preencher uma ComboBox com os valores de uma enumeração, já que não posso forçar a chamada da ComboBoxGetDescription .

O que eu quero tem os seguintes requisitos:

  • A leitura (HowNice)myComboBox.selectedItemretornará o valor selecionado como o valor da enumeração.
  • O usuário deve ver as seqüências de exibição amigáveis, e não apenas o nome dos valores da enumeração. Então, em vez de ver " NotNice", o usuário verá " Not Nice At All".
  • Felizmente, a solução exigirá alterações mínimas de código nas enumerações existentes.

Obviamente, eu poderia implementar uma nova classe para cada enumeração que criar e substituir a dela ToString(), mas isso dá muito trabalho para cada enumeração e prefiro evitar isso.

Alguma ideia?

Heck, eu vou até dar um abraço como uma recompensa :-)

Shalom Craimer
fonte
1
jjnguy está certo de que as enumerações de Java resolvem isso muito bem ( javahowto.blogspot.com/2006/10/… ), mas isso é de relevância questionável.
Matthew Flaschen
8
Java Enums são uma piada. Talvez eles adicionem propriedades em 2020: /
Chad Grant
Para uma solução mais leve (mas sem dúvida menos robusta), veja meu tópico .
Gutblender

Respostas:

42

Você pode escrever um TypeConverter que leia os atributos especificados para procurá-los em seus recursos. Assim, você obteria suporte em vários idiomas para nomes de exibição sem muito aborrecimento.

Examine os métodos ConvertFrom / ConvertTo do TypeConverter e use a reflexão para ler atributos nos campos de enumeração .

peneira
fonte
OK, escrevi um código (veja minha resposta a esta pergunta) - você acha que é o suficiente, estou perdendo alguma coisa?
Shalom Craimer 28/04/09
1
Agradável. Melhor overal, mas pode ser um exagero para o seu software comum que nunca será globalizado de qualquer maneira. (Eu sei, esse pressuposto vai passar a ser falso mais tarde ;-).)
peSHIr
85

ComboBoxtem tudo o que você precisa: a FormattingEnabledpropriedade para a qual você deve definir truee o Formatevento em que você precisará colocar a lógica de formatação desejada. Portanto,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Anton Gogolev
fonte
Isso funciona apenas com caixas de combinação de banco de dados? Não consigo acionar o evento Format caso contrário.
SomethingBetter
o único problema aqui é que você não pode ter a lista ordenada com a sua lógica
GorillaApe
Esta é uma otima soluçao. Eu precisaria trabalhar com um DataGridComboBoxColumnpensamento. Alguma chance de resolvê-lo? Eu não sou capaz de encontrar uma maneira de obter acesso ao ComboBoxdo DataGridComboBoxColumn.
Soko
46

Não! Enums são primitivas e não objetos da interface do usuário - fazê-las servir a interface do usuário em .ToString () seria muito ruim do ponto de vista do design. Você está tentando resolver o problema errado aqui: o problema real é que você não deseja que Enum.ToString () apareça na caixa de combinação!

Agora, este é realmente um problema muito solucionável! Você cria um objeto de interface do usuário para representar seus itens de caixa de combinação:

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

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

E adicione apenas instâncias dessa classe à coleção Items da sua caixa de combinação e defina estas propriedades:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
Sander
fonte
1
Eu concordo plenamente. Você também não deve expor o resultado de ToString () à interface do usuário. E você não obtém localização.
Øyvind Skaar
Eu sei que isso é antigo, mas como isso é diferente?
Nportelli 28/02
2
Eu vi uma solução semelhante em que , em vez de usar uma classe personalizada, eles mapearam os valores de enum para a Dictionarye usaram as propriedades Keye Valuecomo DisplayMembere ValueMember.
Jeff B
42

TypeConverter. Eu acho que é isso que eu estava procurando. Todos saudam Simon Svensson !

[TypeConverter(typeof(EnumToStringUsingDescription))]
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Tudo o que preciso alterar na minha enumeração atual é adicionar esta linha antes da declaração.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Depois que eu fizer isso, qualquer enumeração será exibida usando os DescriptionAttributecampos.

Ah, e o TypeConverterseria definido assim:

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

Isso me ajuda com o meu caso ComboBox, mas obviamente não substitui o ToString(). Acho que vou me contentar com isso enquanto isso ...

Shalom Craimer
fonte
3
Você está manipulando Enum -> String, mas também precisará de Enum> InstanceDescriptor e String -> Enum se desejar uma implementação completa. Mas acho que exibi-lo é suficiente para suas necessidades no momento. ;)
sisve
1
Esta solução só funciona quando suas descrições são estáticas, infelizmente.
Llyle
A propósito, o uso do TypeConverter não está vinculado a descrições estáticas, o coverter pode preencher valores de outras fontes que não sejam atributos.
Dmitry Tashkinov
3
Puxa meu cabelo há algumas horas, mas ainda não parece funcionar mesmo em aplicativos simples de console. Decorei o enum [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, tentei fazer Console.WriteLine(MyEnum.One)e ele ainda sai como "One". Você precisa de alguma mágica especial como TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(que funciona bem)?
Dav
1
@ scraimer Publiquei uma versão do seu código que suporta sinalizadores. Todos os direitos reservados a você ...
Avi Turner
32

Usando seu exemplo de enumeração:

using System.ComponentModel;

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

Crie uma extensão:

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

Então você pode usar algo como o seguinte:

HowNice myEnum = HowNice.ReallyNice;
string myDesc = myEnum.Description();

Consulte: http://www.blackwasp.co.uk/EnumDescription.aspx para obter mais informações. O crédito vai para Richrd Carr pela solução

Tyler Durden
fonte
Eu segui os detalhes no site referido e usei-o da seguinte maneira, parece direto para mim 'string myDesc = HowNice.ReallyNice.Description ();' myDesc irá produzir Really Nice
Ananda
8

Você pode criar uma estrutura genérica que você pode usar para todas as suas enumerações com descrições. Com conversões implícitas de e para a classe, suas variáveis ​​ainda funcionam como a enumeração, exceto para o método 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;
    }

}

Exemplo de uso:

Described<HowNice> nice = HowNice.ReallyNice;

Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"
Guffa
fonte
5

Eu não acho que você pode fazer isso sem simplesmente vincular a um tipo diferente - pelo menos, não de forma conveniente. Normalmente, mesmo que você não possa controlar ToString(), você pode usar a TypeConverterpara formatar de forma personalizada - mas o IIRC System.ComponentModelnão respeita isso por enumerações.

Você pode vincular a uma string[]das descrições ou a algo essencialmente como um par de chave / valor? (descrição / valor) - algo como:

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

E depois vincular a EnumWrapper<HowNice>.GetValues()

Marc Gravell
fonte
1
O nome 'GetDescription' não existe no contexto atual. estou usando o .NET 4.0
Muhammad Adeel Zahid
@MuhammadAdeelZahid olha atentamente para o início da pergunta - que vem do post vinculado: stackoverflow.com/questions/479410/enum-tostring
Marc Gravell
Desculpe, mas não consigo obter nenhuma pista da pergunta. seu método não está compilando e mostra o erro.
Muhammad Adeel Zahid
Olá Marc, tentei sua ideia. Está funcionando, mas theComboBox.SelectItemé o tipo de EnumWrapper<T>, em vez de Tsi mesmo. Eu acho que o scraimer quer Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
22412 Peter Lee
5

A melhor maneira de fazer isso é fazer uma aula.

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

Eu acredito que é a melhor maneira de fazê-lo.

Quando colocado em caixas de combinação, o bonito ToString será mostrado, e o fato de que ninguém pode criar mais instâncias de sua classe torna-o um enum.

ps pode haver algumas pequenas correções de sintaxe, não sou muito bom com c #. (Cara de Java)

jjnguy
fonte
1
Como isso ajuda no problema da caixa de combinação?
peSHIr
Bem, agora, quando o novo objeto é colocado em uma caixa de combinação, seu ToString será exibido corretamente e a classe ainda age como uma enumeração.
jjnguy
1
Teria sido a minha resposta também.
Mikko Rantanen
3
E vendo como o pôster original explicitamente não queria uma aula. Não acho que uma aula seja muito mais trabalhosa. Você pode abstrair a descrição e o ToString substituir uma classe pai para todas as enumerações. Depois disso, tudo o que você precisa é de um construtor private HowNice(String desc) : base(desc) { }e dos campos estáticos.
Mikko Rantanen
Eu esperava evitar isso, pois significa que toda e qualquer enumeração que eu fizer exigirá sua própria classe. Ugh.
Shalom Craimer
3

Como você prefere não criar uma classe para cada enum, recomendo criar um dicionário do valor da enum / exibir o texto e vinculá-lo.

Observe que isso depende dos métodos do método GetDescription na postagem original.

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;
}
Richard Szalay
fonte
Boa ideia. Mas como eu usaria isso com uma caixa de combinação? Depois que o usuário seleciona um item da caixa de combinação, como sei qual dos itens ele selecionou? Pesquisar pela string Descrição? Isso faz com que a minha pele coçar ... (pode haver uma string "colisão" entre a descrição cordas)
Shalom Craimer
A chave do item selecionado será o valor real da enumeração. Além disso, não colide as seqüências de descrição - como o usuário dirá a diferença?
Richard Szalay
<cont> se você tiver seqüências de descrições que colidem, não deve vincular os valores da enumeração diretamente a uma caixa de combinação.
Richard Szalay
hmmm ... Bem, você poderia me dar um exemplo de código sobre como você adicionaria itens à caixa de combinação? Tudo o que posso pensar é "foreach (string s em descriptionsDict.Values) {this.combobox.Items.Add (s);}"
Shalom Craimer
1
ComboBox.DataSource = dicionário;
Richard Szalay
3

Não é possível substituir o ToString () de enumerações em C #. No entanto, você pode usar métodos de extensão;

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

Claro que você terá que fazer uma chamada explícita para o método, ie;

HowNice.ReallyNice.ToString(0)

Esta não é uma solução agradável, com uma instrução switch e tudo mais - mas deve funcionar e espero que sem muitas reescritas ...

Björn
fonte
Esteja ciente de que o controle que se liga à sua enum não chamaria esse método de extensão, chamaria a implementação padrão.
Richard Szalay
Certo. Portanto, essa é uma opção viável, se você precisar de uma descrição em algum lugar, ela não ajuda no problema da caixa de combinação.
peSHIr
Um problema maior é que isso nunca será chamado (como método de extensão) - métodos de instância que já existem sempre têm prioridade.
Marc Gravell
Claro que Marc está certo (como sempre?). Minha experiência com o .NET é mínima, mas fornecer um parâmetro fictício para o método deve funcionar. Resposta editada.
Björn
2

A seguir à resposta do @scraimer, aqui está uma versão do conversor de tipo enum para string, que também suporta sinalizadores:

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

}

E um método de extensão para usá-lo:

    /// <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;
    }
Avi Turner
fonte
1

Eu escreveria uma classe genérica para uso com qualquer tipo. Eu usei algo assim no passado:

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
}

Além disso, você pode adicionar um "método de fábrica" ​​estático para criar uma lista de itens da caixa de combinação com um tipo de enumeração (praticamente o mesmo que o método GetDescriptions que você possui). Isso pouparia a necessidade de implementar uma entidade por cada tipo de enum e também forneceria um local agradável / lógico para o método auxiliar "GetDescriptions" (pessoalmente, eu chamaria FromEnum (T obj) ...

Dan C.
fonte
1

Crie uma coleção que contenha o que você precisa (como objetos simples que contêm uma Valuepropriedade que contém o HowNicevalor de enum e uma Descriptionpropriedade que contémGetDescription<HowNice>(Value) e vincula o conjunto de dados a essa coleção.

Um pouco assim:

Combo.DataSource = new EnumeratedValueCollection<HowNice>();
Combo.ValueMember = "Value";
Combo.DisplayMember = "Description";

quando você tem uma classe de coleção como esta:

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

}

Como você pode ver, essa coleção é facilmente personalizável com os lambda para selecionar um subconjunto do seu enumerador e / ou implementar uma formatação personalizada para, em stringvez de usar a GetDescription<T>(x)função mencionada.

peSHIr
fonte
Excelente, mas estou procurando por algo que exija ainda menos trabalho no código.
Shalom Craimer
Mesmo se você puder usar a mesma coleção genérica para esse tipo de coisa para todos os seus enumeradores? Eu não estava sugerindo escrever uma coleção dessas para cada enum, é claro.
peSHIr
1

Você pode usar o PostSharp para direcionar o Enum.ToString e adicionar o código de adição que desejar. Isso não requer nenhuma alteração no código.

majkinetor
fonte
1

O que você precisa é transformar uma enumeração em um ReadonlyCollection e vincular a coleção à caixa de combinação (ou qualquer controle ativado por par de valor-chave).

Primeiro, você precisa de uma classe para conter os itens da lista. Como tudo o que você precisa é do par int / string, sugiro usar uma interface e uma combinação de classes base para que você possa implementar a funcionalidade em qualquer objeto que desejar:

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
}

Aqui está a interface e uma classe de amostra que a implementa. Observe que a chave 'da classe é fortemente digitada no Enum e que as propriedades IValueDescritionItem são implementadas explicitamente (para que a classe possa ter quaisquer propriedades e você pode ESCOLHER as que implementam o Par de chave / valor.

Agora a classe 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)];
        }
    }

}

Então, tudo que você precisa no seu código é:

private EnumToReadOnlyCollection<MyItem, HowNice> enumcol;
enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>();
comboBox1.ValueMember = "Niceness";
comboBox1.DisplayMember = "NicenessDescription";
comboBox1.DataSource = enumcol;

Lembre-se de que sua coleção é digitada com MyItem, portanto o valor da caixa de combinação deve retornar um valor de enumeração se você vincular à propriedade apropriada.

Adicionei a propriedade T this [Enum t] para tornar a coleção ainda mais útil do que um simples consumível de combinação, por exemplo textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

É claro que você pode optar por transformar MyItem em uma classe Key / Value usada apenas para essa puprose, pulando efetivamente MyItem nos argumentos de tipo EnumToReadnlyCollection por completo, mas você será forçado a usar int para a chave (ou seja, obter a caixa de combinação1.SelectedValue retornaria int e não o tipo enum). Você pode contornar isso se criar uma classe KeyValueItem para substituir MyItem e assim por diante ...


fonte
1

Desculpe por ter esta discussão antiga.

Eu seguiria o caminho a seguir para localizar enum, pois ele pode exibir valores significativos e localizados para o usuário, não apenas a descrição, através de um campo de texto da lista suspensa neste exemplo.

Primeiro, eu crio um método simples chamado OwToStringByCulture para obter seqüências localizadas de um arquivo de recursos globais; neste exemplo, é BiBongNet.resx na pasta App_GlobalResources. Dentro desse arquivo de recurso, verifique se todas as strings são iguais aos valores da enumeração (ReallyNice, SortOfNice, NotNice). Nesse método, passo o parâmetro: resourceClassName, que geralmente é o nome do arquivo de recurso.

Em seguida, crio um método estático para preencher uma lista suspensa com enum como fonte de dados, chamada OwFillDataWithEnum. Este método pode ser usado com qualquer enum posteriormente.

Em seguida, na página com uma lista suspensa chamada DropDownList1, defina no Page_Load a seguinte linha de código simples para preencher a enumeração na lista suspensa.

 BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1, "BiBongNet");

É isso aí. Eu acho que com alguns métodos simples como esses, você pode preencher qualquer controle de lista com qualquer enumeração, não apenas com valores descritivos, mas também com texto localizado para exibição. Você pode fazer todos esses métodos como métodos de extensão para melhor uso.

Espero que esta ajuda. Compartilhe para ser compartilhado!

Aqui estão os métodos:

public class BiBongNet
{

        enum HowNice
        {
            ReallyNice,
            SortOfNice,
            NotNice
        }

        /// <summary>
        /// This method is for filling a listcontrol,
        /// such as dropdownlist, listbox... 
        /// with an enum as the datasource.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ctrl"></param>
        /// <param name="resourceClassName"></param>
        public static void OwFillDataWithEnum<T>(ListControl ctrl, string resourceClassName)
        {
            var owType = typeof(T);
            var values = Enum.GetValues(owType);
            for (var i = 0; i < values.Length; i++)
            {
                //Localize this for displaying listcontrol's text field.
                var text = OwToStringByCulture(resourceClassName, Enum.Parse(owType, values.GetValue(i).ToString()).ToString());
                //This is for listcontrol's value field
                var key = (Enum.Parse(owType, values.GetValue(i).ToString()));
                //add values of enum to listcontrol.
                ctrl.Items.Add(new ListItem(text, key.ToString()));
            }
        }

        /// <summary>
        /// Get localized strings.
        /// </summary>
        /// <param name="resourceClassName"></param>
        /// <param name="resourceKey"></param>
        /// <returns></returns>
        public static string OwToStringByCulture(string resourceClassName, string resourceKey)
        {
                return (string)HttpContext.GetGlobalResourceObject(resourceClassName, resourceKey);
        }
}
BiBongNet
fonte
1
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Para resolver isso, você deve usar um método de extensão e uma matriz de seqüências de caracteres da seguinte maneira:

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

Código simples e decodificação rápida.

Sérgio
fonte
a variável strings deve ser estática e declarada da seguinte forma: String estática [] strings = new [] {...}
Sérgio
O único problema com isso é que você precisará ter uma função para cada enumeração, e a descrição será parte da própria enumeração ...
Avi Turner
1

Eu tentei essa abordagem e funcionou para mim.

Criei uma classe de wrapper para enumerações e sobrecarreguei o operador implícito para poder atribuí-la a variáveis ​​de enumeração (no meu caso, tive que vincular um objeto a um ComboBox valor).

Você pode usar a reflexão para formatar os valores enum da maneira que deseja, no meu caso, recupero o DisplayAttribute valores de enum (se existentes).

Espero que isto ajude.

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

EDITAR:

Apenas no caso, eu uso a seguinte função para obter os enumvalores que eu uso para o DataSourcedoComboBox

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;
    }
}
Ezequiel Moneró Thompson
fonte
0

Depois de ter o GetDescriptionmétodo (ele precisa ser estático global), você poderá usá-lo através de um método de extensão:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}
temor
fonte
0
Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
}

Status = ReallyNice.GetDescription()
user1308805
fonte
3
Bem-vindo ao stackoverflow! É sempre melhor para fornecer uma breve descrição de um código de exemplo para melhorar a precisão pós :)
Picrofo Software
-1

Você pode definir Enum como

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

e depois use HowNice.GetStringValue().

Vrushal
fonte
2
Isso não compila (eu tenho o .NET 3.5). Onde é declarado 'StringValue'?
temor
1
A resposta de @scraimer é a mesma, exceto que ele está usando um atributo fora da estrutura, enquanto você usa algum tipo de atributo auto-definido.
Oliver