Mostrar los valores de una enumeración en un control del tipo [ListBox] o [ComboBox] y recuperarlos es bastante sencillo, pero existen casos en las que trabajar con ellas se vuelve un poco farragoso.
En una situación normal, lo que se hace es mostrar los valores de las constantes enumeradas en un control ListBox, y a continuación se recoge la opción seleccionada por el usuario, se convierte a contante enumerada y se emplea en un [Select Case] para que dirija la lógica del programa en función de la selección del usuario.
Escribir los elementos de una enumeracion en un control ComboBox
El código básico para hacerlo es el siguiente:
#Region "Forma rápida de trabajar con una enumeración y un combo box" '-------------------------------------------- ' Ver mas info en esta Url ' http://www.codeproject.com/Tips/160838/Quick-And-Dirty-Option-Lists-using-Enums.aspx '------------------------------------------- ''' <summary> ''' Los dedos de una mano ''' </summary> Public Enum DedosE None Pulgar Índice Corazón Anular Menique End Enum '--------------------------------------------- ' Escuchador Form.Load '--------------------------------------------- Private Sub Form2_Load( _ sender As System.Object, e As System.EventArgs) _ Handles MyBase.Load ' Cargar el combo Box con los valores de la enumeracion ComboBox1.Items.AddRange(System.Enum.GetNames(GetType(DedosE))) End Sub '--------------------------------------------- ' Escuchador SelectedIndexChanged '--------------------------------------------- Private Sub ComboBox1_SelectedIndexChanged( _ sender As System.Object, e As System.EventArgs) _ Handles ComboBox1.SelectedIndexChanged ' ------------------------------------------- ' Obtener el valor seleccionado como texto ' ------------------------------------------- Dim nombreDedo As String nombreDedo = System.Enum.GetName(GetType(DedosE), ComboBox1.SelectedIndex) Me.TextBox1.Text = ("Seleccionado: " & nombreDedo) ' ------------------------------------------- ' Obener el valor seleccionado como constante enumerada ' ------------------------------------------- ' 1 - obtener el 'nombre' de la constante enumerada ' (Esta en la variable [nombreDedo]) ' 2 - Convertir el valor 'Nombre' en una constante enumerada Dim dedoElegido As DedosE dedoElegido = CType(System.Enum.Parse(GetType(DedosE), nombreDedo), DedosE) ' 3 - Usar la constante enumerada Select Case dedoElegido Case DedosE.None '''' Operaciones diversas Case DedosE.Pulgar '''' Operaciones diversas Case DedosE.Índice '''' Operaciones diversas '------- '------- Case Else Throw New System.ComponentModel.InvalidEnumArgumentException( _ dedoElegido.ToString, dedoElegido, GetType(DedosE)) End Select End Sub #End Region
El problema surge cuando los valores de la enumeración no se pueden leer fácilmente porque o bien son palabras compuestas o por alguna otra razón.
En este caso, el problema que aparece es que el texto que tengo que situar en el [ListBox], no coincide con los valores de las constantes enumeradas, y hacer el cambio de un texto a la constante se complica mucho, demasiado diría yo.
Este es un problema común y he visto que muchos programadores han tratado de resolverlo usando para ello técnicas diferentes. En mi caso, después de mucho mirar y mirar, he adaptado la solución que propone:
Y que en esencia consiste en usar un atributo [DescriptionAttribute] para situar el texto 'legible por las personas' en cada elemento de la enumeración, de forma que usando [Reflexión] pueda recuperar ese texto a partir del valor de la constante enumerada, y al contrario, que teniendo el valor del texto del atributo pueda localizar la constante enumerada que lo contiene.
Por ejemplo, la enumeración [DedosE] quedaría de la siguiente manera
'--------------------------------- ' Una Observación ' El texto del elemento [summary] lo usa el IDE de Visual Estudio ' El texto de [Description(Atribute)] se usa por el código del ' que estamos tratando en este documento '--------------------------------- ''' <summary> ''' Los dedos de una mano ''' </summary> <System.ComponentModel.Description( _ Enumeración que describe los nombres de los dedos de una mano")> _" Public Enum DedosE '---------------- ''' <summary> ''' Ningun dedo ''' </summary> <System.ComponentModel.Description("Ningún dedo")> _ None '---------------- ''' <summary> ''' El dedo gordo ''' </summary> <System.ComponentModel.Description( _ "El dedo gordo (o pulgar)")> _ Pulgar '---------------- ''' <summary> ''' El dedo Índice ''' </summary> <System.ComponentModel.Description( _ "El dedo con el que se apunta (o índice)")> _ Indice '---------------- ''' <summary> ''' El dedo corazón ''' </summary> <System.ComponentModel.Description( _ "El dedo más largo (o corazón)")> _ Corazon '---------------- ''' <summary> ''' El dedo del anillo ''' </summary> <System.ComponentModel.Description( _ "El dedo del anillo (o anular)")> _ Anular '---------------- ''' <summary> ''' El dedo currin ''' </summary> <System.ComponentModel.Description( _ "El dedo más pequeño (o menique)")> _ Menique End Enum
La tarea que quiero hacer es recuperar uno de los textos del atributo [DescriptionAttribute] de un elemento de la enumeracon a partir del valor "name" del la constante enumerada, es decir que si escribo 'Corazón' la función me devuelva la cadena 'El dedo más largo (o corazón)' y la función que se muestra a continuación hace precisamente eso
''' <summary> ''' Devuelve el valor del atributo [DescriptionAttribute] ''' para este elemento de la enumeracion ''' Si el atributo no existe, se devuelve el valor ''' 'name' de la constante enumerada '''</summary> '''<param name="constanteEnumeracion"> ''' <para>Tipo <see cref="System.Enum">System.Enum</see></para> ''' Un elemento de la enumeración, una constante enumerada ''' (Se espera un valor del tipo 'DedosE.Pulgar', ''' o por ejemplo ' Color.AliceBlue') ''' </param> '''<returns> ''' <para>Tipo: <see cref="System.String">String</see></para> ''' <para>Dos opciones: </para> ''' <para>Si para esta constante enumerada existe el atributo ''' [DescriptionAttribute] se devuelve el texto asociado al mismo.</para> ''' <para>Si no existe, se devuelve se devuelve el valor ''' 'name' de la constante enumerada</para> ''' </returns> '''<remarks> ''' <example> ''' Codigo copiado y adaptado de ''' http://www.codeproject.com/Tips/101247/Human-readable-strings-for-enum-elements.aspx ''' <code> ''' Public Enum DedosE ''' <System.ComponentModel.Description("Ningun dedo")>; None ''' <System.ComponentModel.Description("El dedo gordo (o pulgar)")>Pulgar ''' <System.ComponentModel.Description("El dedo con el que se apunta (o indice)")>Indice ''' <System.ComponentModel.Description("El dedo mas largo (o corazón)")> Corazon ''' <System.ComponentModel.Description("El dedo del anillo (o anular)")> Anular ''' <System.ComponentModel.Description("El dedo mas pequeño (o menique)")> Menique ''' End Enum ''' ''' dim s as string s = DedosE.Pulgar.ToString() ''' s & " : " + GetTextoDescriptionAttribute(DedosE.Pulgar) ''' MessageBox.Show(s) ''' ''' ' El mensaje de salida sera: "Pulgar : El dedo gordo (o pulgar)" ''' </code> ''' </example> '''</remarks> Public Shared Function GetDescriptionAttribute(ByVal constanteEnumeracion As System.Enum) As String ' Obtener la informacion de la constante enumerada Dim fi As Reflection.FieldInfo fi = constanteEnumeracion.GetType.GetField(constanteEnumeracion.ToString) ' Obtener la escripcion del atributo Dim attributes() As System.ComponentModel.DescriptionAttribute = _ CType(fi.GetCustomAttributes(GetType(System.ComponentModel.DescriptionAttribute), False), _ System.ComponentModel.DescriptionAttribute()) Dim resultado As String If (attributes.Length > 0) Then ' El atributo [DescriptionAttribute] existe, devolverlo resultado = attributes(0).Description Else ' No existe, devolver el nombre de la constante enumerada resultado = constanteEnumeracion.ToString End If '------------------------- Return resultado End Function
Lo que quiero obtener se muestra en la siguiente Imagen, y para entenderla debes volver a mirar el código de la enumeración [DedosE], es decir, quiero situar en el control el valor de los atributos de las constantes enumeradas.
Atributos de una enumeración mostrados en un control ListBox
Pero tenemos un problema, esta función [GetDescriptionAttribute] solo recupera un elemento y yo quiero recuperar todos los elementos, es decir, necesito escribir una función diferente que haga el trabajo que quiero, que recupere todos los atributos en un Array para cargarlos en un ListBox (o ComboBox)
El código que permite escribir los atributos en un control (en esta imagen es un ListBox) es el siguiente:
Me.ListBox1.Items.AddRange(Util.Enumeraciones.UtilEnumHelper.GetDescriptionAttributes (GetType(DedosE)))
Y el código de la función [GetDescriptionAttributes] es el siguiente
''' <summary> ''' Recupera una matriz con los valores [Description] de los ''' atributos [AtributeDescription] que decoran ''' cada uno de las constantes enumeradas de la enumeración ''' </summary> ''' <param name="enumType"> ''' <para> Tipo: <see cref="System.Type">System.Type</see></para> ''' <para> Tipo de la enumeración. Se espera un valor del tipo ''' [GetType(NombreEnumeracion)] ''' o bien [constanteDeLaEnumeracion.GetType]. </para> ''' </param> ''' <returns> ''' <para> Tipo <see cref="System.Array">System.Array</see></para> ''' <para> Una matriz que contiene los nombres de lo atributos ''' [DescriptionAtribute] de las constantes enumeradas ''' de la enumeración.</para> ''' <para> Si un elemento no tiene el atributo [DescriptionAtribute] ''' se devuelve el valor 'name' de la constante enumerada</para> ''' <para> Los elementos de la matriz se ordenan en función de los ''' valores binarios de las constantes de enumeración.</para> '''</returns> ''' <exception cref="ArgumentNullException">El valor de enumType es Nothing</exception> ''' <exception cref="ArgumentException">El parámetro enumType no es Enum. </exception> Public Shared Function GetDescriptionAttributes(ByVal enumType As Type) As String() '----------------------------------------- ' Control de parámetros If enumType Is Nothing Then Throw New ArgumentNullException( _ "enumType", _ "La Enumeración recibida tiene un valor nothing") End If If Not (GetType(System.Enum) = enumType.BaseType) Then Throw New ArgumentException( _ "El parámetro enumType no es Enum", "enumType") End If '----------------------------------------- Dim listaDeDescriptionAttributes As New System.Collections.ArrayList Dim enumValores As Reflection.FieldInfo() = enumType.GetFields() Dim fi As Reflection.FieldInfo For Each fi In enumValores If Not fi.IsSpecialName Then ' recuperar el valor numérico ( no se usa) ' Dim valor As Integer = CInt(fi.GetValue(0)) '---- ' Recuperar el nombre del elemento Dim nombre As String = fi.Name ' ---- ' Recuperar los atributos de este elemento Dim attributes() As System.ComponentModel.DescriptionAttribute = _ CType(fi.GetCustomAttributes(GetType(System.ComponentModel.DescriptionAttribute), False), _ System.ComponentModel.DescriptionAttribute()) '---- If (attributes.Length > 0) Then ' El atributo [DescriptionAttribute] existe, devolverlo listaDeDescriptionAttributes.Add(attributes(0).Description) Else ' No existe, devolver el nombre de la constante enumerada listaDeDescriptionAttributes.Add(nombre) End If End If Next '-------------------------------------- ' convertir la lista en un array '------------------------------------------------------ ' http://msdn.microsoft.com/es-es/library/fcyyh2hb.aspx ' Copies the elements of the ArrayList to a string array. ' Copia los elementos de un [ArrayList] en un [Array] de cadenas Dim resultado As String() resultado = CType(listaDeDescriptionAttributes.ToArray(GetType(String)), String()) '-------------------------------------- ' eliminar objetos usados '-------------------------------------- listaDeDescriptionAttributes = Nothing enumValores = Nothing fi = Nothing '-------------------------------------- Return resultado End Function
Ahora tengo que resolver el problema contrario, dado un texto que puede ser un nombre de una constante enumerada o el valor de su atributo [DescriptionAtribute], lo que necesito es obtener el valor de la constante enumerada
Y para eso he escrito la función [ValueOf] que es la que realiza esa tarea
''' <summary> ''' Obtiene la constante enumerada a partir de la cadena de texto que la representa ''' </summary> ''' <param name="enumType"> ''' <para> Tipo: <see cref="System.Type">System.Type</see></para> ''' <para> Tipo de la enumeración. Se espera un valor del tipo ''' [GetType(NombreEnumeracion)] ''' o bien [constanteDeLaEnumeracion.GetType] . </para> ''' </param> ''' <param name="value"> ''' <para> Tipo: <see cref="System.String">System.String</see></para> ''' <para> Cadena que representa:</para> ''' <para> El text [Descripcion] del atributo [DescriptionAttribute] ''' de una de las constantes enumeradas de la enumeración.</para> ''' <para> O bien, </para> ''' <para> El valor [Name] de una de las constantes enumeradas</para> '''</param> ''' <param name="ignoreCase"> ''' <para> Tipo: <see cref="System.Boolean">System.Boolean</see></para> ''' <para> true para no distinguir entre mayúsculas y minúsculas; </para> ''' <para> false para tener en cuenta la distinción entre ''' mayúsculas y minúsculas. </para> ''' </param> ''' <returns> ''' <para> Tipo: <see cref="System.Object">System.Object</see></para> ''' <para> Constante enumerada obtenida a partir de la cadena de ''' texto que la representa.</para> ''' <para> Dado el texto [Description], del atributo [DescriptionAttribute] ''' (Por ejemplo: "El dedo gordo (o pulgar)") ''' o el texto de la constante enumerada (Por ejemplo: "Pulgar") ''' me devuelve la constante enumerada que lo contiene ''' (Por ejemplo: DedosE.Pulgar) ''' </para> '''</returns> ''' <exception cref="ArgumentNullException"> ''' El valor de enumType o value es Nothing. ''' </exception> ''' <exception cref="ArgumentException"> ''' <para> El parámetro enumType no es un objeto Enum.</para> ''' <para> O bien </para> ''' <para> value es una cadena vacía ("") o ''' sólo contiene un espacio en blanco.</para> ''' <para> O bien </para> ''' <para> value es un nombre, pero no una de las constantes ''' con nombre definidas para la enumeración. </para> '''</exception> ''' <exception cref="OverflowException"> ''' value está fuera del intervalo del tipo subyacente de enumType. ''' </exception> ''' <remarks> ''' <para> ''' El parámetro value contiene una cadena que contiene uno de estos dos ''' valores: El valor [Descripcion] de un atributo [DescriptionAttribute] ''' de una de las constantes enumeradas definidas para la enumeración. ''' O bien, ''' El valor [Name] de una de las constantes enumeradas ''' </para> ''' <para> ''' A partir de la cadena Value, se obtiene el valor de la constante enumerada ''' </para> ''' <example> ''' <code> ''' public enum MyEnum ''' { ''' [DescriptionAttribute("Una cadena explicativa y legible por una persona")] ''' AMachineReadableEnum, ''' } ''' ... ''' MyEnum me = ValueOf(MyEnum)("Una cadena explicativa y legible por una persona"); ''' MyEnum me = MyEnum.AMachineReadableEnum; ''' ''' Las dos instrucciones anteriores son equivalentes ''' </code> ''' </example> ''' </remarks> Private Shared Function ValueOf( _ ByVal enumType As Type, _ ByVal value As String, _ ByVal ignoreCase As Boolean) As Object '------------------------------------------------ ' - Esta función imita el funcionamiento de una función [Enum.Parse] ' - Convierte una cadena en un elemento de la enumeración ' - La cadena contiene uno de estos dos valores ' El valor [Descripcion] de un atributo [DescriptionAttribute] de una ' de las constantes con nombre definidas para la enumeración. ' O bien, ' El valor [Name] de una de las constantes con nombre definidas para la enumeración ' ' - La cadena se busca en primer lugar entre la cadena ' de texto los atributos [DescriptionAttribute] ' de cada elemento de la enumeración, y si no se ' encuentra se busca entre los nombres de los valores de la enumeración. ' - Después de hacer la búsqueda ' - Si encuentra el texto se devuelve SIEMPRE el valor ' del elemento de la enumeración, es decir un valor enumerado ' - Si no se encuentra se devuelve la excepción [ArgumentException] ' como hace una función [Enum.Parse] '------------------------------------------------ ' Problema encontrado ' --2011-12-20 ' * He intentado reunir todos los [Return] en uno solo, asignando el ' valor parcial de cada bucle de búsqueda a una variable de tipo(T). ' El problema que me he encontrado es que aunque la declare con ' valor [Nothing], si el valor no se encuentra, la variable que recoge ' el resultado toma el primer valor de la enumeración, y no hay ' forma de saber si es un valor real o el valor por defecto. ' * Por esa razón he dejado el código así, con tres salidas ' posibles que aunque no es muy bonito sí que funciona '------------------------------------------------ '------------------------- ' Paso UNO 'Control de parametros If enumType Is Nothing Then Throw New ArgumentNullException( _ "enumType", " 'enumType' tiene el valor Nothing ") End If If Not (GetType(System.Enum) = enumType.BaseType) Then Throw New ArgumentException( _ "El parámetro enumType no es Enum", "enumType") End If '----------- ' El control del texto lo separo porque simula la función Parse y ' se generan estos errores en estos casos específicos If value Is Nothing Then Throw New ArgumentNullException( _ "value", " La cadena especificada tiene el valor Nothing ") End If If String.IsNullOrWhiteSpace(value) = True Then Throw New ArgumentException( _ " La cadena especificada es una cadena vacía o sólo contiene un espacio en blanco", _ "value") End If '------------------------- ' Paso DOS ' Preparar las variables internas de la función ' '----------------------- ' Dim enumType As Type = GetType(T) Dim nombres() As String = System.Enum.GetNames(enumType) ' '------------------------- ' la forma de comparación por defecto es ignorar las mayúsculas y minúsculas ' salvo que se especifique lo contrario en el parámetro [ignoreCase] ' - true para no distinguir entre mayúsculas y minúsculas; ' - false para tener en cuenta la distinción entre mayúsculas y minúsculas. Dim formaDeComparacion As StringComparison = StringComparison.CurrentCultureIgnoreCase If ignoreCase = False Then formaDeComparacion = StringComparison.CurrentCulture End If '------------------------- ' Paso TRES ' ' Supongo que he recibido un texto de un atributo [DescriptionAttribute] ' y lo busco entre los atributos ' si lo encuentro devuelvo el nombre del 'Elemento de la enumeración', ' la constante enumerada que lo contiene For Each name As String In nombres Dim atributo As String atributo = GetDescriptionAttribute(CType(System.Enum.Parse(enumType, name), System.Enum)) If atributo.Equals(value, formaDeComparacion) Then ' ¡Encontrado! ----> Salir de la función Return System.Enum.Parse(enumType, name) End If Next '------------------------- ' Paso CUATRO ' ' No encontrada la descripción del atributo ' Supongo que he recibido un texto que corresponde al nombre de un elemento de la enumeración ' si lo encuentro devuelvo el 'Elemento de la enumeración' ' si no lo encuentro devuelvo una excepción ' buscar la cadena ente los nombres de la enumeración For Each name As String In nombres If name.Equals(value, formaDeComparacion) = True Then ' ¡Encontrado! ----> Salir de la función Return System.Enum.Parse(enumType, name) End If Next '------------------------- ' Paso CINCO ' ' no encontrada Error Throw New ArgumentException( _ " La cadena especificada[" & value & "]" & Environment.NewLine & _ " no coincide con ningún valor de los descritos" & Environment.NewLine & _ " en los atributos [DescriptionAttribute] de la enumeración." & Environment.NewLine & _ " y tampoco es una de las constantes con nombre" & Environment.NewLine & _ " definidas para la enumeración.", _ "value") End Function
Por último queda describir las funciones Parse y TryParse que usan esta función de forma trasparente
Public Shared Function Parse( _ ByVal enumType As Type, _ ByVal value As String, _ ByVal ignoreCase As Boolean) As Object ' Llamar a la función que hace el trabajo Return ValueOf(enumType, value, ignoreCase) End Function
Public Overloads Shared Function TryParse( _ ByVal enumType As Type, _ ByVal value As String, _ ByVal ignoreCase As Boolean, _ <Runtime.InteropServices.Out()> ByRef outputConstanteEnumeracion As Object, _ <Runtime.InteropServices.Out()> ByRef outputMensajeError As String) _ As Boolean Dim resultadoConversion As Boolean outputMensajeError = String.Empty outputConstanteEnumeracion = Nothing Try '--------------------------------------------------------------- ' Excepciones que devuelve la funcion ValueOf ' Que es la función que hace el trabajo '--------------------------------------------------------------- ' - ArgumentNullException - El valor de enumType o value es Nothing. ' - ArgumentException ' El parámetro enumType no es un objeto Enum. ' O bien ' value es una cadena vacía ("") o sólo contiene un espacio en blanco. ' O bien ' value es un nombre, pero no una de las constantes con nombre definidas para la enumeración. ' - OverflowException - value está fuera del intervalo del tipo subyacente de enumType. '--------------------------------------------------------------- outputConstanteEnumeracion = ValueOf(enumType, value, ignoreCase) resultadoConversion = True outputMensajeError = String.Empty Catch ex As ArgumentNullException outputConstanteEnumeracion = Nothing resultadoConversion = False outputMensajeError = ex.Message Catch ex As ArgumentException outputConstanteEnumeracion = Nothing resultadoConversion = False outputMensajeError = ex.Message Catch ex As OverflowException 'Excepción que se produce cuando una operación aritmética, 'de conversión de tipos o de conversión de otra naturaleza 'en un contexto comprobado, da como resultado una sobrecarga. outputConstanteEnumeracion = Nothing resultadoConversion = False outputMensajeError = ex.Message Catch ex As Exception outputConstanteEnumeracion = Nothing resultadoConversion = False outputMensajeError = ex.Message '------------------------------------- 'Ignoro la regla FxCop 'CA1031: No capturar los tipos de excepción general 'http://msdn.microsoft.com/library/ms182137(VS.100).aspx '------------------------------------- End Try Return resultadoConversion End Function
Estas funciones, y algunas mas, las tengo guardadas en una clase que he llamado [UtilEnumHelper] y que copio en la solución cada vez que tengo que trabajar con enumeraciones, de esta forma no tengo que pensar más en escribir código