Reputation: 579
How can I bind the formatted string descriptions of an enumeration to a combobox? I want the combobox to show the descriptions, not the enum.
I have an enum like this:
Public Enum Sandwiches
<ComponentModel.Description("Ham Sandwich")> HamSandwich
<ComponentModel.Description("Reuben")> Reuben
<ComponentModel.Description("Po’ Boy")> PoBoy
<ComponentModel.Description("Grilled Cheese")> GrilledCheese
End Enum
I can use this function to convert the selected item back to an enum, after the description string has already been bound to a combobox:
Public Function GetEnumFromDescriptionAttribute(Of T)(description As String) As T
Dim type As Type = GetType(T)
If Not type.IsEnum Then
Throw New InvalidOperationException()
End If
For Each fi As Reflection.FieldInfo In type.GetFields()
Dim descriptionAttribute As ComponentModel.DescriptionAttribute = TryCast(Attribute.GetCustomAttribute(fi, GetType(ComponentModel.DescriptionAttribute)), ComponentModel.DescriptionAttribute)
If descriptionAttribute IsNot Nothing Then
If descriptionAttribute.Description <> description Then
Continue For
End If
Return DirectCast(fi.GetValue(Nothing), T)
End If
If fi.Name <> description Then
Continue For
End If
Return DirectCast(fi.GetValue(Nothing), T)
Next
Return Nothing
End Function
But I cannot find a clean way to bind all of the enumerations to the combobox. I know that an often sugested solution is to convert the enums and enum descriptions to a dictionary, and then set those as the cbo DisplayMember and ValueMember. But I cannot figure out how to actually do that.,
I've ready through dozens of partial solutions on how to do this; The problem is that they are all written in C#, use implicit conversion with Option Strict turned OFF, and don't show the full implementation. So it's impossible for me to translate any of the solutions into .NET, since I have no idea what type variables have been defined as.
Upvotes: 1
Views: 646
Reputation: 11801
It appears that your goal is override the string representation of a Enum value. The base representation displays Enum's name, where-as you want to display the Enum's Description attribute.
Assuming that this is a WinForm's combobox, the standard way would be to decorate the Enum with a custom TypeConverter and override the ConvertTo
method. Using the EnumConverter Class and the basis for the custom converter minimizes the required code.
Imports System.ComponentModel
Imports System.Globalization
Imports System.Reflection
Public Class EnumDescriptionConverter(Of T As {Structure, IConvertible}) : Inherits EnumConverter
Private enumDescriptions As New Dictionary(Of T, String)
Public Sub New()
MyBase.New(GetType(T))
' Since the ability to add an Enum constraint to the generic type does not exist in VB.
' this check is to ensure that it is an Enum.
' Note that normally, throwing an exception in a constructor is considered bad form, but
' there is no other option
If GetType(T).IsEnum Then
LoadEnumDescriptions()
Else
Throw New ArgumentException($"{GetType(T).Name} is not an Enum")
End If
End Sub
Private Sub LoadEnumDescriptions()
' An Enum type comprises static (Shared) fields that represent the defined enum values and
' an instance field that holds thae actual value. so only retrieve the static fields
' to get the defined (named) enum members.
For Each fi As FieldInfo In GetType(T).GetFields(BindingFlags.Public Or BindingFlags.Static)
Dim description As String
Dim descAttrib As DescriptionAttribute = fi.GetCustomAttribute(Of DescriptionAttribute)
If descAttrib Is Nothing Then
' no description attribute so use the defined name
description = fi.Name
Else
description = descAttrib.Description
End If
enumDescriptions.Add(CType(fi.GetValue(Nothing), T), description)
Next
End Sub
Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As CultureInfo, value As Object, destinationType As Type) As Object
Dim ret As Object
' the purpose of this converter is to provide an enum's description attribute as the string representation
' instead of the field name as provided by the standard implemention.
If destinationType Is GetType(String) AndAlso value IsNot Nothing Then
Dim enumValue As T = DirectCast(value, T)
If enumDescriptions.ContainsKey(enumValue) Then
ret = enumDescriptions(enumValue)
Else
ret = enumValue.ToString(Nothing)
End If
Else
ret = MyBase.ConvertTo(context, culture, value, destinationType)
End If
Return ret
End Function
End Class
Modify your Enum definition to use this custom converter.
<TypeConverter(GetType(EnumDescriptionConverter(Of Sandwiches)))>
Public Enum Sandwiches
<ComponentModel.Description("Ham Sandwich")> HamSandwich
<ComponentModel.Description("Reuben")> Reuben
<ComponentModel.Description("Po’ Boy")> PoBoy
<ComponentModel.Description("Grilled Cheese")> GrilledCheese
End Enum
Now all you need to do is load the ComboBox.Items
with Enum values.
ComboBox1.Items.AddRange(System.Enum.GetValues(GetType(Sandwiches)).Cast(Of Object).ToArray())
Use the ComboBox.SelectedItem
to retrieve the selected value.
Upvotes: 1
Reputation: 54417
Here's a class that I wrote some time ago to represent an Enum
value and its description:
Imports System.ComponentModel
Imports System.Reflection
''' <summary>
''' Contains an enumerated constant value and a friendly description of that value, if one exists.
''' </summary>
''' <typeparam name="T">
''' The enumerated type of the value.
''' </typeparam>
Public Class EnumDescriptor(Of T)
''' <summary>
''' The friendly description of the value.
''' </summary>
Private _description As String
''' <summary>
''' The enumerated constant value.
''' </summary>
Private _value As T
''' <summary>
''' Gets the friendly description of the value.
''' </summary>
''' <value>
''' A <b>String</b> containing the value's Description attribute value if one exists; otherwise, the value name.
''' </value>
Public ReadOnly Property Description() As String
Get
Return Me._description
End Get
End Property
''' <summary>
''' Gets the enumerated constant value.
''' </summary>
''' <value>
''' An enumerated constant of the <b>EnumDescriptor's</b> generic parameter type.
''' </value>
Public ReadOnly Property Value() As T
Get
Return Me._value
End Get
End Property
''' <summary>
''' Creates a new instance of the <b>EnumDescriptor</b> class.
''' </summary>
''' <param name="value">
''' The value to provide a description for.
''' </param>
Public Sub New(ByVal value As T)
Me._value = value
'Get the Description attribute.
Dim field As FieldInfo = value.GetType().GetField(value.ToString())
Dim attributes As DescriptionAttribute() = DirectCast(field.GetCustomAttributes(GetType(DescriptionAttribute), _
False), _
DescriptionAttribute())
'Use the Description attribte if one exists, otherwise use the value itself as the description.
Me._description = If(attributes.Length = 0, _
value.ToString(), _
attributes(0).Description)
End Sub
''' <summary>
''' Overridden. Creates a string representation of the object.
''' </summary>
''' <returns>
''' The friendly description of the value.
''' </returns>
Public Overrides Function ToString() As String
Return Me.Description
End Function
End Class
Here's a simplified version I just wrote:
Imports System.ComponentModel
Imports System.Reflection
Public Class EnumDescriptor(Of T)
Public ReadOnly Property Description As String
Public ReadOnly Property Value As T
Public Sub New(value As T)
Me.Value = value
'Get the Description attribute.
Dim field = value.GetType().GetField(value.ToString())
Dim attributes = DirectCast(field.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
'Use the Description attribute if one exists, otherwise use the value itself as the description.
Description = If(attributes.Length = 0, value.ToString(), attributes(0).Description)
End Sub
Public Overrides Function ToString() As String
Return Description
End Function
End Class
Here's another class that can be used to store an instance of the previous class for each value in an Enum
:
''' <summary>
''' A collection of EnumDescriptors for an enumerated type.
''' </summary>
''' <typeparam name="T">
''' The type of the enumeration for which the EnumDescriptors are created.
''' </typeparam>
Public Class EnumDescriptorCollection(Of T)
Inherits ObjectModel.Collection(Of EnumDescriptor(Of T))
''' <summary>
''' Creates a new instance of the <b>EnumDescriptorCollection</b> class.
''' </summary>
Public Sub New()
'Populate the collection with an EnumDescriptor for each enumerated value.
For Each value As T In [Enum].GetValues(GetType(T))
Items.Add(New EnumDescriptor(Of T)(value))
Next
End Sub
End Class
Here's some example usage that binds a instance of the collection class to a ComboBox
:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
With ComboBox1
.DisplayMember = "Description"
.ValueMember = "Value"
.DataSource = New EnumDescriptorCollection(Of Sandwiches)
End With
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
MessageBox.Show(ComboBox1.SelectedValue.ToString(), ComboBox1.Text)
End Sub
End Class
Upvotes: 2