c# clase - ¿Cómo tengo un combo box enlazado enum con formato de cadena personalizado para valores enum?




declarar class (18)

No es posible sobrescribir ToString () de 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 ...

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

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 sintaxis como:

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

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

Lo que quiero tiene los siguientes requisitos:

  • La lectura (HowNice)myComboBox.selectedItem devolverá el valor seleccionado como el valor enum.
  • 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 NotNice 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 anular su ToString() , pero eso es mucho trabajo para cada enumeración, y prefiero evitar eso.

¿Algunas ideas?

Diablos, incluso lanzaré un hug como una recompensa :-)


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 del cuadro combinado con un tipo de enumeración (más o menos el mismo que el método GetDescriptions que tiene). Esto le evitaría tener que implementar una entidad por cada tipo de enumeración, y también proporcionaría un lugar agradable / lógico para el método de ayuda "GetDescriptions" (personalmente lo llamaría FromEnum (T obj) ...


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


¡No! Los enumerados son primitivos y no objetos de la interfaz de usuario, lo que hace que sirvan a la interfaz de usuario en .ToString () sería bastante malo desde el punto de vista del diseño. Está tratando de resolver el problema incorrecto aquí: ¡el verdadero problema es que no quiere que Enum.ToString () aparezca en el cuadro combinado!

¡Ahora este es un problema muy solvente! Crea un objeto UI 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 solo agregue instancias de esta clase a la colección Elementos de su cuadro combinado y establezca estas propiedades:

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

Podría hacer una estructura genérica que podría usar para todas sus enumeraciones que tengan descripciones. Con las conversiones implícitas hacia y desde la clase, sus variables todavía funcionan como la enumeración a excepción del 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"

Cree una colección que contenga lo que necesita (como objetos simples que contengan una propiedad Value contenga el valor enum de HowNice y una propiedad Description contenga GetDescription<HowNice>(Value) y una combinación de datos y combo para 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 string lugar de utilizar la función GetDescription<T>(x) que menciona.


Intenté este enfoque y funcionó para mí.

Creé una clase contenedora para enums y sobrecargué el operador implícito para poder asignarlo a las variables enum (en mi caso tuve que vincular un objeto a un valor ComboBox ).

Puede usar la reflexión para formatear los valores enum de la manera que desee, en mi caso recupero DisplayAttribute de los valores enum (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:

Just in case, I use the following function to get the enum values that I use for the DataSource of the 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;
    }
}

No creo que puedas hacerlo sin simplemente vincular a un tipo diferente, al menos no convenientemente. Normalmente, incluso si no puede controlar ToString() , puede usar un TypeConverter para hacer un formateo personalizado, pero IIRC el material de System.ComponentModel no respeta esto para las enumeraciones.

¿Podría enlazar a una string[] de las descripciones, o algo esencialmente como un par de clave / valor? (descripción / valor) - algo así 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 enlazar a EnumWrapper<HowNice>.GetValues()


You can define Enum as

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

and then use HowNice.GetStringValue() .


Once you have the GetDescription method (it needs to be global static), you could use this through an extension method:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}

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, así:

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.


ComboBox tiene todo lo que necesita: la propiedad FormattingEnabled , que debe establecer en true , y Format event, 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);
    }

TypeConverter. Creo que esto es lo que estaba buscando. ¡Saludos, 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 hago eso, cualquier enumeración se mostrará con el DescriptionAttribute de sus campos.

Ah, y TypeConverter se 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 de ComboBox, pero obviamente no reemplaza a ToString() . Supongo que me conformaré con esto mientras tanto ...


Puede utilizar PostSharp para apuntar a Enum.ToString y agregar todos los códigos que desee. Esto no requiere ningún cambio de código.


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

Tenga en cuenta que esto tiene una dependencia en 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;
}

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

Status = ReallyNice.GetDescription()

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 rellena en comboboxes, se mostrará el bonito ToString, y el hecho de que nadie pueda hacer más instancias de tu clase esencialmente lo convierte en una enumeración.

Es posible que haya algunas pequeñas correcciones de sintaxis, no soy muy bueno con C #. (Chico de Java)


Una forma simple y genérica de convertir una enumeración a algo con lo que puedes interactuar:

public static Dictionary<int, string> ToList<T>() where T : struct
{
   return ((IEnumerable<T>)Enum
       .GetValues(typeof(T)))
       .ToDictionary(
           item => Convert.ToInt32(item),
           item => item.ToString());
}

Y entonces:

var enums = EnumHelper.ToList<MyEnum>();






c# combobox enums