variable - tipos de datos en c# visual studio




¿Cómo tengo un combo box enlazado enum con formato de cadena personalizado para valores enum? (14)

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


¡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";

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.


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


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

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

Primero, necesitas una clase para contener los elementos de la lista. Como todo lo que necesita es el par int / string, le sugiero que use 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 tipada a Enum, y que las propiedades de IValueDescritionItem se implementan explícitamente (por lo que la clase puede tener cualquier propiedad y usted 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 necesitas en tu 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 se escribe con MyItem para que el valor del cuadro combinado devuelva un valor enum si se vincula a la propiedad adecuada.

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

Por supuesto, puede optar por convertir MyItem en una clase Key / Value utilizada solo para esta puprose omitiendo MyItem en los argumentos de tipo de EnumToReadnlyCollection en total, pero luego estaría forzado a ir con int para la clave (lo que significa obtener combobox1.SelectedValue devolvería int y no el tipo enum). Trabaja en eso si crea una clase KeyValueItem para reemplazar MyItem y así sucesivamente ...


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


Perdón por obtener este hilo viejo.

Me gustaría ir de la siguiente manera para localizar enum, ya que puede mostrar valores significativos y localizados para el 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 completar una lista desplegable con enum como fuente de datos, llamada OwFillDataWithEnum. Este método se puede usar con cualquier enumeración posterior.

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 completar cualquier control de lista con cualquier enumeración, no solo como valores descriptivos sino también como 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 ser compartido!

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

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"

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.


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

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

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


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

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


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.





enums