¿Cómo tengo un cuadro combinado enlazado enum con formato de cadena personalizado para valores de enumeración?

135

En la publicación Enum ToString , se describe un método para usar el atributo personalizado DescriptionAttributecomo este:

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

Y luego, llamas a una función GetDescription, usando una sintaxis como:

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

Pero eso realmente no me ayuda cuando simplemente quiero llenar un ComboBox con los valores de una enumeración, ya que no puedo forzar al ComboBox a llamarGetDescription .

Lo que quiero tiene los siguientes requisitos:

  • La lectura (HowNice)myComboBox.selectedItemdevolverá el valor seleccionado como el valor de enumeración.
  • El usuario debe ver las cadenas de visualización fáciles de usar, y no solo el nombre de los valores de enumeración. Entonces, en lugar de ver " NotNice", el usuario vería " Not Nice At All".
  • Con suerte, la solución requerirá cambios mínimos de código en las enumeraciones existentes.

Obviamente, podría implementar una nueva clase para cada enumeración que creo y anularla ToString(), pero eso es mucho trabajo para cada enumeración, y prefiero evitar eso.

¿Algunas ideas?

Diablos, incluso te daré un abrazo como recompensa :-)

Shalom Craimer
fuente
1
jjnguy es correcto que las enumeraciones de Java resuelven esto muy bien ( javahowto.blogspot.com/2006/10/… ), pero eso es de relevancia cuestionable.
Matthew Flaschen
8
Las enumeraciones de Java son una broma. Tal vez agregarán propiedades en 2020: /
Chad Grant
Para una solución más ligera (pero posiblemente menos robusta) vea mi hilo .
Gutblender

Respuestas:

42

Podría escribir un TypeConverter que lea los atributos especificados para buscarlos en sus recursos. Por lo tanto, obtendría soporte multilingüe para nombres de visualización sin mucha molestia.

Mire los métodos ConvertFrom / ConvertTo de TypeConverter y use la reflexión para leer los atributos en sus campos de enumeración .

sisve
fuente
OK, escribí un código (mira mi respuesta a esta pregunta). ¿Crees que es suficiente? ¿Me estoy perdiendo algo?
Shalom Craimer
1
Buena esa. Mejor en general, pero podría ser una exageración para su software habitual que nunca se globalizará de ninguna manera. (Lo sé, esa suposición resultará ser falsa más tarde. ;-))
peSHIr
85

ComboBoxtiene todo lo que necesita: la FormattingEnabledpropiedad, que debe establecer truey el Formatevento, donde deberá colocar la lógica de formato deseada. Así,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Anton Gogolev
fuente
¿Esto solo funciona con cuadros combinados enlazados a datos? De lo contrario, no puedo hacer que se active el evento Format.
Algo mejor el
el único problema aquí es que no puedes ordenar la lista con tu lógica
GorillaApe
Esta es una gran solución. Aunque lo necesitaría para trabajar DataGridComboBoxColumn. ¿Alguna oportunidad de resolverlo? No puedo encontrar una manera de acceder a ComboBoxla DataGridComboBoxColumn.
Soko
46

No lo hagas Las enumeraciones son primitivas y no objetos de la interfaz de usuario; hacerlas servir a la interfaz de usuario en .ToString () sería bastante malo desde el punto de vista del diseño. Está intentando resolver el problema incorrecto aquí: ¡el problema real es que no desea que Enum.ToString () aparezca en el cuadro combinado!

Ahora bien, este es un problema muy solucionable. Crea un objeto de interfaz de usuario para representar los elementos del cuadro combinado:

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

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

Y luego simplemente agregue instancias de esta clase a la colección de elementos de su cuadro combinado y establezca estas propiedades:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
Lijadora
fuente
1
Estoy totalmente de acuerdo. Tampoco debe exponer el resultado de ToString () a la interfaz de usuario. Y no obtienes localización.
Øyvind Skaar
Sé que esto es viejo, pero ¿cómo es eso diferente?
nportelli
2
He visto una solución similar en la que , en lugar de usar una clase personalizada, asignaron los valores de enumeración a a Dictionaryy usaron las propiedades Keyy Valuecomo DisplayMembery ValueMember.
Jeff B
42

TypeConverter. Creo que esto es lo que estaba buscando. ¡Saluden a Simon Svensson !

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

Todo lo que necesito cambiar en mi enumeración actual es agregar esta línea antes de su declaración.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Una vez que haga eso, cualquier enumeración se mostrará utilizando DescriptionAttributesus campos.

Ah, y TypeConverterse definiría así:

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

Esto me ayuda con mi caso ComboBox, pero obviamente no anula el ToString(). Supongo que me conformaré con esto mientras tanto ...

Shalom Craimer
fuente
3
Está manejando Enum -> String, pero también necesitará Enum> InstanceDescriptor y String -> Enum si desea una implementación completa. Pero supongo que mostrarlo es suficiente para sus necesidades en este momento. ;)
sisve
1
Esta solución solo funciona cuando sus descripciones son estáticas, desafortunadamente.
Llyle 01 de
Por cierto, el uso de TypeConverter no está vinculado a descripciones estáticas, el convertidor puede poblar valores de otras fuentes que no sean atributos.
Dmitry Tashkinov
3
He estado tirando de mi cabello durante unas horas, pero todavía no parece funcionar incluso en aplicaciones de consola simples. Decoré la enumeración con [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, intenté hacerlo Console.WriteLine(MyEnum.One)y todavía sale como "Uno". ¿Necesitas magia especial como TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(que funciona bien)?
Dav
1
@scraimer He publicado una versión de su código que admite banderas. todos los derechos reservados para usted ...
Avi Turner
32

Usando su ejemplo de enumeración:

using System.ComponentModel;

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

Crea una extensión:

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

Entonces puedes usar algo como lo siguiente:

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

Ver: http://www.blackwasp.co.uk/EnumDescription.aspx para más información. El crédito va a Richrd Carr por la solución

Tyler Durden
fuente
Seguí los detalles en el sitio referido y lo usé de la siguiente manera, me parece sencillo 'string myDesc = HowNice.ReallyNice.Description ();' myDesc emitirá Really Nice
Ananda
8

Podría crear una estructura genérica que podría usar para todas sus enumeraciones que tenga descripciones. Con conversiones implícitas hacia y desde la clase, sus variables aún funcionan como la enumeración, excepto por el 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;
    }

}

Ejemplo de uso:

Described<HowNice> nice = HowNice.ReallyNice;

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

No creo que pueda hacerlo sin simplemente vincularse a un tipo diferente, al menos, no convenientemente. Normalmente, incluso si no puede controlar ToString(), puede usar un TypeConverterpara formatear de forma personalizada, pero IIRCSystem.ComponentModel no respeta esto para las enumeraciones.

¿Podría unirse a una string[]de las descripciones, o algo esencialmente como un par clave / valor? (descripción / 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));
    }
}

Y luego unirse a EnumWrapper<HowNice>.GetValues()

Marc Gravell
fuente
1
El nombre 'GetDescription' no existe en el contexto actual. estoy usando .NET 4.0
Muhammad Adeel Zahid
@MuhammadAdeelZahid observa de cerca el comienzo de la pregunta, que proviene de la publicación vinculada: stackoverflow.com/questions/479410/enum-tostring
Marc Gravell
lo siento pero no puedo obtener ninguna pista de la pregunta. su método no se está compilando y muestra el error.
Muhammad Adeel Zahid
Hola Marc, probé tu idea. Está funcionando, pero theComboBox.SelectItemes un tipo de EnumWrapper<T>, en lugar de Tsí mismo. Creo que scraimer quiere Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
Peter Lee
5

La mejor manera de hacer esto es hacer una clase.

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

Creo que esa es la mejor manera de hacerlo.

Cuando se rellenan en cuadros combinados, se mostrará el bonito ToString, y el hecho de que nadie pueda hacer más instancias de su clase esencialmente lo convierte en una enumeración.

PD: es posible que deba haber algunas pequeñas correcciones de sintaxis, no soy muy bueno con C #. (Chico Java)

jjnguy
fuente
1
¿Cómo ayuda esto con el problema del cuadro combinado?
peSHIr
Bueno, ahora, cuando el nuevo objeto se coloca en un cuadro combinado, su ToString se mostrará correctamente y la clase seguirá actuando como una enumeración.
jjnguy
1
Habría sido mi respuesta también.
Mikko Rantanen
3
Y viendo cómo el cartel original explícitamente no quería una clase. No creo que una clase sea mucho más trabajo. Puede abstraer la descripción y anular ToString a una clase primaria para todas las enumeraciones. Después de esto, todo lo que necesita es un constructor private HowNice(String desc) : base(desc) { }y los campos estáticos.
Mikko Rantanen
Esperaba evitar esto, ya que significa que cada enumeración que haga requerirá su propia clase. Ugh
Shalom Craimer
3

Dado que preferiría no crear una clase para cada enumeración, recomendaría crear un diccionario del valor de enumeración / texto de visualización y vincularlo en su lugar.

Tenga en cuenta que esto depende de los métodos del método GetDescription en la publicación 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
fuente
Buena idea. Pero, ¿cómo usaría esto con un cuadro combinado? Una vez que el usuario selecciona un elemento del cuadro combinado, ¿cómo sé cuál de los elementos seleccionó? ¿Buscar por la cadena de descripción? Eso hace que mi piel pica ... (que podría haber una "colisión" cuerda entre cadenas de descripción)
Shalom Craimer
La clave del elemento seleccionado será el valor de enumeración real. Además, no choque las cadenas de descripción: ¿cómo notará el usuario la diferencia?
Richard Szalay
<cont> si tiene cadenas de descripción que colisionan, entonces no debería vincular los valores de la enumeración a un cuadro combinado directamente de todos modos.
Richard Szalay
hmmm ... Bueno, ¿podrías darme un código de ejemplo sobre cómo agregarías elementos al cuadro combinado? Todo lo que puedo pensar es "foreach (cadena s en descripcionesDict.Values) {this.combobox.Items.Add (s);}"
Shalom Craimer
1
ComboBox.DataSource = diccionario;
Richard Szalay
3

No es posible anular el ToString () de las enumeraciones en C #. Sin embargo, puede usar métodos de extensión;

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

Por supuesto, tendrá que hacer una llamada explícita al método, es decir;

HowNice.ReallyNice.ToString(0)

Esta no es una buena solución, con una declaración de cambio y todo, pero debería funcionar y, con suerte, sin muchas reescrituras ...

Björn
fuente
Tenga en cuenta que el control que se une a su enumeración no llamaría a este método de extensión, llamaría a la implementación predeterminada.
Richard Szalay
Correcto. Entonces, esta es una opción viable si necesita una descripción en alguna parte, no ayuda con el problema del cuadro combinado.
peSHIr
Un problema mayor es que esto nunca se llamará (como método de extensión); los métodos de instancia que ya existen siempre tienen prioridad.
Marc Gravell
Por supuesto, Marc tiene razón (¿como siempre?). Mi experiencia .NET es mínima, pero proporcionar un parámetro ficticio para el método debería ser suficiente. Respuesta editada
Björn
2

Siguiendo con la respuesta @scraimer, aquí hay una versión del convertidor de tipo enum a string, que también admite indicadores:

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

}

Y un método de extensión para usarlo:

    /// <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
fuente
1

Escribiría una clase genérica para usar con cualquier tipo. He usado algo como esto en el pasado:

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
}

Además de esto, puede agregar un "método de fábrica" ​​estático para crear una lista de elementos de cuadro combinado con un tipo de enumeración (más o menos lo mismo que el método GetDescriptions que tiene allí). Esto le ahorrará tener que implementar una entidad por cada tipo de enumeración, y también proporcionará un lugar agradable / lógico para el método auxiliar "GetDescriptions" (personalmente lo llamaría FromEnum (T obj) ...

Dan C.
fuente
1

Cree una colección que contenga lo que necesita (como objetos simples que contengan una Valuepropiedad que contenga el HowNicevalor de enumeración y una Descriptionpropiedad que contenga GetDescription<HowNice>(Value)y combine el combo con esa colección.

Un poco como esto:

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

cuando tienes una clase de colección 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 puede ver, esta colección se puede personalizar fácilmente con lambda para seleccionar un subconjunto de su enumerador y / o implementar un formato personalizado en stringlugar de usar la GetDescription<T>(x)función que menciona.

peSHIr
fuente
Excelente, pero estoy buscando algo que requiera aún menos trabajo en el código.
Shalom Craimer
¿Incluso si puede usar la misma colección genérica para este tipo de cosas para todos sus enumeradores? Por supuesto, no sugería escribir una colección personalizada para cada enumeración.
peSHIr
1

Puede usar PostSharp para apuntar a Enum.ToString y agregar el código adicional que desee. Esto no requiere ningún cambio de código.

majkinetor
fuente
1

Lo que necesita es convertir una enumeración en una colección ReadonlyCollection y vincular la colección al cuadro combinado (o cualquier control habilitado de par clave-valor para el caso)

En primer lugar, necesita una clase para contener los elementos de la lista. Dado que todo lo que necesita es el par int / string, sugiero usar una interfaz y un combo de clase base para que pueda implementar la funcionalidad en cualquier objeto que desee:

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
}

Aquí está la interfaz y una clase de muestra que lo implementa. Tenga en cuenta que la clave de la clase está fuertemente tipeada en la enumeración, y que las propiedades IValueDescritionItem se implementan explícitamente (por lo que la clase puede tener las propiedades y puede ELEGIR las que implementan Par clave / valor.

Ahora la clase 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)];
        }
    }

}

Entonces, todo lo que necesita en su código es:

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

Recuerde que su colección está escrita con MyItem, por lo que el valor del cuadro combinado debe devolver un valor de enumeración si se vincula a la propiedad apropiada.

Agregué la propiedad T this [Enum t] para hacer que la colección sea aún más útil que un simple consumible combinado, por ejemplo textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

Por supuesto, puede optar por convertir MyItem en una clase Key / Value utilizada solo para este puprose omitiendo efectivamente MyItem en los argumentos de tipo de EnumToReadnlyCollection por completo, pero luego se vería obligado a ir con int para la clave (lo que significa obtener combobox1.SelectedValue devolvería int y no el tipo de enumeración). Evita eso si crea una clase KeyValueItem para reemplazar MyItem y así sucesivamente ...


fuente
1

Perdón por tener este viejo hilo.

Iría de la siguiente manera para localizar la enumeración, ya que puede mostrar valores significativos y localizados al usuario, no solo la descripción, a través de un campo de texto de lista desplegable en este ejemplo.

Primero, creo un método simple llamado OwToStringByCulture para obtener cadenas localizadas de un archivo de recursos globales, en este ejemplo es BiBongNet.resx en la carpeta App_GlobalResources. Dentro de este archivo de recursos, asegúrese de tener todas las cadenas iguales a los valores de la enumeración (ReallyNice, SortOfNice, NotNice). En este método, paso el parámetro: resourceClassName, que generalmente es el nombre del archivo de recursos.

A continuación, creo un método estático para llenar una lista desplegable con enum como fuente de datos, llamada OwFillDataWithEnum. Este método se puede usar con cualquier enumeración más adelante.

Luego, en la página con una lista desplegable llamada DropDownList1, configuré en Page_Load la siguiente línea de código simple para completar la enumeración de la lista desplegable.

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

Eso es. Creo que con algunos métodos simples como estos, puede llenar cualquier control de lista con cualquier enumeración, no solo con valores descriptivos sino con texto localizado para mostrar. Puede hacer todos estos métodos como métodos de extensión para un mejor uso.

Espero que esto ayude. ¡Comparte para compartir!

Aquí están los 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
fuente
1
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Para resolver esto, debe usar un método de extensión y una matriz de cadenas de la siguiente manera:

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 simple y decodificación rápida.

Sergio
fuente
la variable de cadenas debe ser Estática y declarada así: Cadena estática [] cadenas = nueva [] {...}
Sérgio
El único problema con esto es que necesitará tener una función para cada enumeración, y la descripción será aparte de la enumeración misma ...
Avi Turner
1

Intenté este enfoque y funcionó para mí.

Creé una clase de contenedor para enumeraciones y sobrecargué el operador implícito para poder asignarlo a variables de enumeración (en mi caso tuve que vincular un objeto a un ComboBoxvalor).

Puede usar la reflexión para formatear los valores de enumeración de la manera que desee, en mi caso recupero los DisplayAttributevalores de enumeración (si existen).

Espero que esto ayude.

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:

Por si acaso, yo uso la siguiente función para obtener los enumvalores que yo uso para el DataSourcede laComboBox

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
fuente
0

Una vez que tenga el GetDescriptionmétodo (debe ser global estático), puede usarlo a través de un método de extensión:

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

Status = ReallyNice.GetDescription()
usuario1308805
fuente
3
¡Bienvenido a stackoverflow! Siempre es mejor proporcionar una breve descripción de un código de muestra para mejorar la precisión de la publicación :)
Picrofo Software
-1

Puedes definir Enum como

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

y luego usar HowNice.GetStringValue().

Vrushal
fuente
2
Esto no se compila (tengo .NET 3.5). ¿Dónde se declara ´StringValue´?
asombro
1
La respuesta de @scraimer es la misma, excepto que está usando un atributo fuera del marco, mientras que usted usa algún tipo de atributo autodefinido.
Oliver