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

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

 GetDescription(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 aarm como una recompensa 🙂

Puede escribir un TypeConverter que lea los atributos especificados para buscarlos en sus recursos. Por lo tanto, obtendría soporte en varios idiomas para nombres de pantalla sin mucha molestia.

Consulte los métodos ConvertFrom / ConvertTo de TypeConverter y use reflection para leer los atributos en sus campos enum.

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)e.Value); } 

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

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

 [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 …

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

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 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 value) { return new Described(value); } public static implicit operator T(Described value) { return value._value; } } 

Ejemplo de uso:

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

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 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(value); } } public override string ToString() { return Description; } public static EnumWrapper[] GetValues() { T[] vals = (T[])Enum.GetValues(typeof(T)); return Array.ConvertAll(vals, v => new EnumWrapper(v)); } } 

Y luego enlazar a EnumWrapper.GetValues()

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 syntax, no soy muy bueno con C #. (Chico de Java)

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 GetDescriptions() where T : struct { IDictionary values = new Dictionary(); 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; } 

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 statement de cambio y todo, pero debería funcionar y con suerte sin muchas reescrituras …

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

  ///  /// A drop-in converter that returns the strings from ///  /// of items in an enumaration when they are converted to a string, /// like in ToString(). ///  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); } ///  /// Coverts an Enums to string by it's description. falls back to ToString. ///  /// The value. ///  public string EnumToString(Enum value) { //getting the actual values List values = EnumToStringUsingDescription.GetFlaggedValues(value); //values.ToString(); //Will hold results for each value List results = new List(); //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); } ///  /// 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 ///  /// The value. ///  private static List 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 values = new List(); 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:

  ///  /// Converts an Enum to string by it's description. falls back to ToString ///  /// The enum val. ///  public static string ToStringByDescription(this Enum enumVal) { EnumToStringUsingDescription inter = new EnumToStringUsingDescription(); string str = inter.EnumToString(enumVal); return str; } 

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

 public class ComboBoxItem { /// 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) …

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(Value) y una combinación de datos y combo para esa colección.

Un poco como esto:

 Combo.DataSource = new EnumeratedValueCollection(); 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 : ReadOnlyCollection> { public EnumeratedValueCollection() : base(ListConstructor()) { } public EnumeratedValueCollection(Func selection) : base(ListConstructor(selection)) { } public EnumeratedValueCollection(Func format) : base(ListConstructor(format)) { } public EnumeratedValueCollection(Func selection, Func format) : base(ListConstructor(selection, format)) { } internal EnumeratedValueCollection(IList> data) : base(data) { } internal static List> ListConstructor() { return ListConstructor(null, null); } internal static List> ListConstructor(Func format) { return ListConstructor(null, format); } internal static List> ListConstructor(Func selection) { return ListConstructor(selection, null); } internal static List> ListConstructor(Func selection, Func format) { if (null == selection) selection = (x => true); if (null == format) format = (x => GetDescription(x)); var result = new List>(); foreach (T value in System.Enum.GetValues(typeof(T))) { if (selection(value)) { string description = format(value); result.Add(new EnumeratedValue(value, description)); } } return result; } public bool Contains(T value) { return (Items.FirstOrDefault(item => item.Value.Equals(value)) != null); } public EnumeratedValue 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 { 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(x) que menciona.

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.

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 : ReadOnlyCollection where T: IValueDescritionItem,new() where TEnum : struct { Type _type; public EnumToReadOnlyCollection() : base(new List()) { _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 enumcol; enumcol = new EnumToReadOnlyCollection(); 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 …

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(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 } ///  /// This method is for filling a listcontrol, /// such as dropdownlist, listbox... /// with an enum as the datasource. ///  ///  ///  ///  public static void OwFillDataWithEnum(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())); } } ///  /// Get localized strings. ///  ///  ///  ///  public static string OwToStringByCulture(string resourceClassName, string resourceKey) { return (string)HttpContext.GetGlobalResourceObject(resourceClassName, resourceKey); } } 
 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 deencoding rápida.

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 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(); Display = display != null ? String.Format(display.Name, val) : val.ToString(); } public static implicit operator T(EnumItem val) { return val.Value; } public static implicit operator EnumItem(T val) { return new EnumItem(val); } } 

EDITAR:

Por las dudas, uso la siguiente función para obtener los valores enum que uso para el DataSource del ComboBox

 public static class Utils { public static IEnumerable> GetEnumValues() { List> result = new List>(); foreach (T item in Enum.GetValues(typeof(T))) { result.Add(item); } return result; } } 

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

 public static string ToString(this HowNice self) { return GetDescription(self); } 
 Enum HowNice { [StringValue("Really Nice")] ReallyNice, [StringValue("Kinda Nice")] SortOfNice, [StringValue("Not Nice At All")] NotNice } Status = ReallyNice.GetDescription() 

Puedes definir Enum como

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

y luego usa HowNice.GetStringValue() .