Click here to Skip to main content
15,884,472 members
Articles / Programming Languages / Visual Basic

Populating Combo Boxes from Enums

Rate me:
Please Sign up or sign in to vote.
4.73/5 (31 votes)
16 Jun 2016CPOL7 min read 30.8K   845   34   12
A quick method of populating ComboBoxes from Enum's with optional resource strings.

Image 1 Image 2

Introduction

I have always believed that the best programmers are lazy programmers in the sense that they hate repetitious coding and want to find ways to automate and reduce the amount of code that must be produced. One area where this comes into practice is with fixed-value combo boxes that usually translate into values that need to be worked with in code. Most programmers end up coding various kinds of string lists and KeyValuePairs trying to make this work, and end up doing it over and over.

The EnumFunctions module with the accompanying demo form is an attempt to automate this and to do so in a way that is language independent. By the way, the code and examples are provided in both VB.Net and C#.

Background

I usually try to write applications and products that are language and culture independent so that they can be translated to different countries and languages. This means that in practice, I do not store human-readable values (e.g. Strings) in the database but instead rely on bytes with a limited range of values, mostly using Enums in my code. Many programmers simply store the string selected in a drop-down list which is fast, but makes the application difficult to translate and can cause errors from trying to do string comparisons. In contrast, using Enums is easy and allows for type checking to be done at both compile and run time to improve the quality and reliability of the code. This article shows how to easily 'internationalize' your application to support different languages on combo boxes.

Using the Code

First, start with an Enum for the particular set of values you want to work with. I usually use Byte values as this works well for storing it in the database and a drop-down list with more than 128 items would be a bad design. You can also use 16 and 32 bit integers as well.  The problem with just using the names and values from the Enum is that they don't look terribly good to end-users. A list like:

VB.NET
Public Enum PreferredContactMethodEnum As Byte
    HomePhone = 0
    WorkPhone = 1
    CellPhone = 2
    EMail = 3
    Mail = 4
    FAX = 5
    Other = 6
End Enum
C#
public enum PreferredContactMethodEnum : byte
{
    HomePhone = 0,
    WorkPhone = 1,
    CellPhone = 2,
	EMail = 3,
	Mail = 4,
	FAX = 5,
	Other = 6
}

produces a fairly ugly set of choices in the drop-down list if we just extract the list of values and names and assign it to the ComboBox Item list. It turns out that Microsoft has already thought of this and has provided a very nice mechanism to allow more information to be provided using the DisplayAttribute. To use it you need to add a reference to System.ComponentModel.DataAnnotations to your project. We can now prefix each item with an attribute that gives us a nice human-readable text:

VB.NET
<Display(Name:="Work Phone")>
WorkPhone = 1
C#
[Display(Name:="Work Phone")]
WorkPhone = 1,

This will look a little better in our drop-down list, but we need to do some extra work to extract the display text, which is covered later in the article. However, it would be even better if we could make the list language-independent. The DisplayAttribute supports that as well by allowing you to specify the local resources file and the name of a resource in the file:

VB.NET
<Display(ResourceType:=GetType(My.Resources.Resources), name:="ContactMethodWorkPhone")>
WorkPhone = 1
C#
[Display(ResourceType:=GetType(Properties.Resources), name:="ContactMethodWorkPhone")]
WorkPhone = 1,

We can now put all of the definitions in our local Resource file which will automatically load the correct version for a specific language and culture (provided you have created them). So this is a good start, we can now create human-readable language-independent enum's in code. The next step is to load that into a ComboBox or a DataGridViewComboBoxColumn. The code which accompanies this article uses a set of functions in the EnumFunctions module that I have written and have included with the example code and demo.  Version for both VB and C# are included.

In the form provided, I have two records, one created as a record in a Data Set Table and the other as an object that might be stored using Entity Framework. The major difference between them is that Entity Framework (and similar data stores) allow me to save and restore objects which can then have properties that translate numeric fields into Enums. The Data Set Table definition is limited to only using various sizes of integers, so we need to translate our Enum's and use a data-type that matches, but that will still support nullable values.

Our Contact record looks like this, we are keeping it very simple with the MaritalStatus being optional and the PreferredContactMethod being required:

VB.NET
Public Class Contact
    Public Property Name As String
    Public Property MaritalStatus As MaritalStatusEnum?
    Public Property PreferredContactMethod As PreferredContactMethodEnum = PreferredContactMethodEnum.HomePhone
End Class        
C#
public class Contact
{
    public string Name { get; set; }
    public MaritalStatus? { get; set; }
	public PreferredContactMethodEnum PreferredContactMethod = PreferredContactMethodEnum.HomePhone { get; set; }
}

Our data table record just uses bytes for the two enums with the MaritalStatus as nullable and the PreferredContactMethod as non-nullable. We want to make sure that our enum's are translated into the proper data type otherwise our list will not display anything or our values will not 'stick' after being selected. To use the Enum functions for a data table row, the following line is included in the Form_Load and is used to populate the combo box bound to the MaritalStatus field in the dataset:

VB.NET
EnumFunctions.PopulateDropdownlistFromEnum(Of Byte?, MaritalStatusEnum)(DSMaritalStatusComboBox, True)
C#
EnumFunctions.PopulateDropdownlistFromEnum<byte?, MaritalStatusEnum>(DSMaritalStatusComboBox, True);

The generic parameters tell the function that we are using a list which allows for optional values. The MaritalStatusEnum is to be used to populate the list and the AddEmptyValue parameter set to True tells the function to add an extra blank first line with a null value. This function reads the enum values, looks up the resource strings and populates the combo box with KeyValuePair objects that are mapped to the DisplayMember and the ValueMember of the ComboBox. For mandatory fields, it is even easier:

VB.NET
EnumFunctions.PopulateDropdownlistFromEnum(Of Byte, PreferredContactMethodEnum)(DSPreferredContactMethodComboBox)
C#
EnumFunctions.PopulateDropdownlistFromEnum<byte, PreferredContactMethodEnum>(DSPreferredContactMethodComboBox);

In this case, since the field is required, no nulls are allowed, and no optional line is generated. When working with objects, it is even simpler as we can code everything without having to specify a type:

VB.NET
EnumFunctions.PopulateDropdownlistFromEnum(Of MaritalStatusEnum?)(EFMaritalStatusComboBox, True)
EnumFunctions.PopulateDropdownlistFromEnum(Of PreferredContactMethodEnum)(EFPreferredContactMethodComboBox)
C#
EnumFunctions.PopulateDropdownlistFromEnum<MaritalStatusEnum?>(EFMaritalStatusComboBox, True);
EnumFunctions.PopulateDropdownlistFromEnum<PreferredContactMethodEnum>(EFPreferredContactMethodComboBox);

We can pass our Enum directly into the function and it will use that type because our Entity Framework class property has already been declared using the same Enum.  Our combox must have its SelectedValue property bound against the data field as this expects a value and uses it to select the appropriate text from the array.

The Inner Workings

The code uses Reflection to pull the values and attributes from the specified enum type, places them into an array of KeyValuePairs and then uses that array to initialize the CombBox control or the DataGridViewComboBoxColumn. The DisplayMember is set to "Key" and the ValueMember is set to "Value".

To create the list I make use of the Enum's functions to get the list of values and the names from the list. The code is similar to the following, but the Enum is hard-coded rather than passed as a generic type:

VB.NET
Dim values As System.Array = [Enum].GetValues(GetType(MaritalStatusEnum))
Dim list As New List(Of [Enum])(values.Length)
For Each value As [Enum] In values
    list.Add(value)
Next
C#
System.Array values = Enum.GetValues(typeof(MaritalStatusEnum));
List <enum> list = new List>Enum>(values.Length);
foreach (Enum value in values) {
   	list.Add(value);
}

We can also use similar functions to get the list of names. As described above, we use of Microsoft's DisplayAttribute class to annotate our Enum items. The trick to making use of the attributes is to use the Reflection methods to dig the attributes out of each of the Enum items. This is done with a publicly exposed function (so you can use it) that takes an Enum value and returns the DisplayAttribute for it:

VB.NET
Public Function GetEnumDisplayAttribute(Of E)(value As E) As <code>DisplayAttribute</code>
    Dim type As System.Type = value.[GetType]()
    If Not type.IsEnum Then
        Throw New ArgumentException([String].Format("Type '{0}' is not Enum", type))
    End If

    Dim members() As MemberInfo = type.GetMember(value.ToString())
    If members.Length = 0 Then
        Throw New ArgumentException([String].Format("Member '{0}' not found in type '{1}'", value, type.Name))
    End If

    Dim member As MemberInfo = members(0)
    Dim attributes() As Object = member.GetCustomAttributes(GetType(DisplayAttribute),False)
    If attributes.Length = 0 Then
        Return Nothing
    End If

    Dim attribute As DisplayAttribute = DirectCast(attributes(0), DisplayAttribute)
    Return attribute
End Function
C#
public DisplayAttribute GetEnumDisplayAttribute<e>(E value)
{
    System.Type type = value.GetType();
    if (!type.IsEnum) {
	    throw new ArgumentException(String.Format("Type '{0}' is not Enum", type));
    }

    MemberInfo[] members = type.GetMember(value.ToString());
    if (members.Length == 0) {
	    throw new ArgumentException(String.Format("Member '{0}' not found in type '{1}'", value, type.Name));
    }

    MemberInfo member = members(0);
    object[] attributes = member.GetCustomAttributes(typeof(DisplayAttribute), false);
    if (attributes.Length == 0) {
	    return null;
    }

    DisplayAttribute attribute = (DisplayAttribute)attributes(0);
    return attribute;
}

The tricky bit from the above code was in getting a single item out of an Enum. This is accomplished by using the Reflection function GetMember. Also, if I don't find a DisplayAttribute, I return a null so that the calling code can deal with it by just using the Enum item name.

The DisplayAttribute itself handles the heavy lifting of looking up the resource string. Once I have it, I just have to ask it for the related name. If there is a resource, it will return the resource string, if not, it will just return the name. This is done by the following method:

VB.NET
Dim da As DisplayAttribute = GetEnumDisplayAttribute(Of E)(EnumTypeValue)
If da Is Nothing Then
    Return [Enum].GetName(EnumTypeValue.[GetType](), EnumTypeValue)
Else
    Return da.GetDescription()
End If
C#
DisplayAttribute da = GetEnumDisplayAttribute<e>(EnumTypeValue);
if (da == null) {
    return Enum.GetName(EnumTypeValue.GetType(), EnumTypeValue);
   } else {
    return da.GetDescription();
}

Points of Interest

You must remember to set the modifier for the Project Resources Page Access Modifier to Public on the project that contains your Enums, if you are putting the EnumFunctions module in a separate DLL.

Researching and figuring out how attributes work has made me appreciate them and to use them liberally through-out my projects. I use attributes to annotate records for logging purposes - the attributes tell my logger which fields to compare and which to ignore. I use them to decorate my forms with additional information that can be displayed to the user while they are being loaded. I use the Entity Framework/Component attributes to add validations such as 'Required'. I would strongly recommend most developers to read up on these and to find ways to make use of the existing attributes or to create new ones as required.

I used the Telerik code translation service at http://converter.telerik.com/ to do the initial conversion from VB to C#. It is pretty cool and does about 95% of the work. The rest you have to figure out yourself.

If I ever meet the Microsoft Developer responsible for creating the concept of Attributes, I will buy them as many alcoholic beverages as they can handle!

History

V2.0 - Added checks for Nullable Enums being passed as second generic type.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Nota Bene Consulting
Canada Canada
I have been a programmer, IT Consultant, technology architect and product developer since 1976 with a focus primarily on Microsoft-based products such as Visual Studio C# and VB, SQL Server, ASP.NET, etc. I do both product development and application development. I have also worked on products based on Microsoft Office applications, MAC-based products, open-source based systems such as Joomla with both a LAMP and WIMP stack and MySQL. I have even worked on FoxPro based products. My preference is for a nice compiler-based environment and a good solid database engine.

I am currently an itinerant developer-for-hire while also supporting several existing clients' products including a Mortgage Management System, an Investment and RRSP Management System and a set of Tools for Mortgage Brokers.

Comments and Discussions

 
SuggestionYou can also....... Pin
DaveAuld19-Jun-16 1:44
professionalDaveAuld19-Jun-16 1:44 
AnswerRe: You can also....... Pin
Arkitec20-Jun-16 17:08
professionalArkitec20-Jun-16 17:08 
QuestionWhy not use a wrapper class? Pin
George Swan18-Jun-16 6:37
mveGeorge Swan18-Jun-16 6:37 
AnswerRe: Why not use a wrapper class? Pin
Arkitec20-Jun-16 17:14
professionalArkitec20-Jun-16 17:14 
GeneralMy vote of 5 Pin
JohannQ15-Jun-16 12:33
JohannQ15-Jun-16 12:33 
GeneralRe: My vote of 5 Pin
Arkitec15-Jun-16 13:11
professionalArkitec15-Jun-16 13:11 
QuestionMy vote of 4 Pin
Muhammad Shahid Farooq13-Jun-16 23:08
professionalMuhammad Shahid Farooq13-Jun-16 23:08 
AnswerRe: My vote of 4 Pin
Arkitec14-Jun-16 6:59
professionalArkitec14-Jun-16 6:59 
PraiseThank you Pin
Sulieman Mansouri11-Jun-16 5:47
Sulieman Mansouri11-Jun-16 5:47 
GeneralRe: Thank you Pin
Arkitec13-Jun-16 12:16
professionalArkitec13-Jun-16 12:16 
GeneralMy vote of 4 Pin
Shahin Khorshidnia10-Jun-16 20:06
professionalShahin Khorshidnia10-Jun-16 20:06 
GeneralRe: My vote of 4 Pin
Arkitec11-Jun-16 2:33
professionalArkitec11-Jun-16 2:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.