Binding an enum to a WinForms combo box, and then setting it

Viewed 215684

a lot of people have answered the question of how to bind an enum to a combo box in WinForms. Its like this:

comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));

But that is pretty useless without being able to set the actual value to display.

I have tried:

comboBox1.SelectedItem = MyEnum.Something; // Does not work. SelectedItem remains null

I have also tried:

comboBox1.SelectedIndex = Convert.ToInt32(MyEnum.Something); // ArgumentOutOfRangeException, SelectedIndex remains -1

Does anyone have any ideas how to do this?

28 Answers

The code

comboBox1.SelectedItem = MyEnum.Something;

is ok, the problem must reside in the DataBinding. DataBinding assignments occur after the constructor, mainly the first time the combobox is shown. Try to set the value in the Load event. For example, add this code:

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    comboBox1.SelectedItem = MyEnum.Something;
}

And check if it works.

Try:

comboBox1.SelectedItem = MyEnum.Something;

EDITS:

Whoops, you've tried that already. However, it worked for me when my comboBox was set to be a DropDownList.

Here is my full code which works for me (with both DropDown and DropDownList):

public partial class Form1 : Form
{
    public enum BlahEnum
    { 
        Red,
        Green,
        Blue,
        Purple
    }

    public Form1()
    {
        InitializeComponent();

        comboBox1.DataSource = Enum.GetValues(typeof(BlahEnum));

    }

    private void button1_Click(object sender, EventArgs e)
    {
        comboBox1.SelectedItem = BlahEnum.Blue;
    }
}

Let's say you have the following enum

public enum Numbers {Zero = 0, One, Two};

You need to have a struct to map those values to a string:

public struct EntityName
{
    public Numbers _num;
    public string _caption;

    public EntityName(Numbers type, string caption)
    {
        _num = type;
        _caption = caption;
    }

    public Numbers GetNumber() 
    {
        return _num;
    }

    public override string ToString()
    {
        return _caption;
    }
}

Now return an array of objects with all the enums mapped to a string:

public object[] GetNumberNameRange()
{
    return new object[]
    {
        new EntityName(Number.Zero, "Zero is chosen"),
        new EntityName(Number.One, "One is chosen"),
        new EntityName(Number.Two, "Two is chosen")
    };
}

And use the following to populate your combo box:

ComboBox numberCB = new ComboBox();
numberCB.Items.AddRange(GetNumberNameRange());

Create a function to retrieve the enum type just in case you want to pass it to a function

public Numbers GetConversionType() 
{
    EntityName type = (EntityName)numberComboBox.SelectedItem;
    return type.GetNumber();           
}

and then you should be ok :)

This is probably never going to be seen among all the other responses, but this is the code I came up with, this has the benefit of using the DescriptionAttribute if it exists, but otherwise using the name of the enum value itself.

I used a dictionary because it has a ready made key/value item pattern. A List<KeyValuePair<string,object>> would also work and without the unnecessary hashing, but a dictionary makes for cleaner code.

I get members that have a MemberType of Field and that are literal. This creates a sequence of only members that are enum values. This is robust since an enum cannot have other fields.

public static class ControlExtensions
{
    public static void BindToEnum<TEnum>(this ComboBox comboBox)
    {
        var enumType = typeof(TEnum);

        var fields = enumType.GetMembers()
                              .OfType<FieldInfo>()
                              .Where(p => p.MemberType == MemberTypes.Field)
                              .Where(p => p.IsLiteral)
                              .ToList();

        var valuesByName = new Dictionary<string, object>();

        foreach (var field in fields)
        {
            var descriptionAttribute = field.GetCustomAttribute(typeof(DescriptionAttribute), false) as DescriptionAttribute;

            var value = (int)field.GetValue(null);
            var description = string.Empty;

            if (!string.IsNullOrEmpty(descriptionAttribute?.Description))
            {
                description = descriptionAttribute.Description;
            }
            else
            {
                description = field.Name;
            }

            valuesByName[description] = value;
        }

        comboBox.DataSource = valuesByName.ToList();
        comboBox.DisplayMember = "Key";
        comboBox.ValueMember = "Value";
    }


}

This worked for me:

comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));
comboBox1.SelectedIndex = comboBox1.FindStringExact(MyEnum.something.ToString());

None of these worked for me, but this did (and it had the added benefit of being able to have a better description for the name of each enum). I'm not sure if it's due to .net updates or not, but regardless I think this is the best way. You'll need to add a reference to:

using System.ComponentModel;

enum MyEnum
{
    [Description("Red Color")]
    Red = 10,
    [Description("Blue Color")]
    Blue = 50
}

....

    private void LoadCombobox()
    {
        cmbxNewBox.DataSource = Enum.GetValues(typeof(MyEnum))
            .Cast<Enum>()
            .Select(value => new
            {
                (Attribute.GetCustomAttribute(value.GetType().GetField(value.ToString()), typeof(DescriptionAttribute)) as DescriptionAttribute).Description,
                value
            })
            .OrderBy(item => item.value)
            .ToList();
        cmbxNewBox.DisplayMember = "Description";
        cmbxNewBox.ValueMember = "value";
    }

Then when you want to access the data use these two lines:

        Enum.TryParse<MyEnum>(cmbxNewBox.SelectedValue.ToString(), out MyEnum proc);
        int nValue = (int)proc;

I use the following helper method, which you can bind to your list.

    ''' <summary>
    ''' Returns enumeration as a sortable list.
    ''' </summary>
    ''' <param name="t">GetType(some enumeration)</param>
    Public Shared Function GetEnumAsList(ByVal t As Type) As SortedList(Of String, Integer)

        If Not t.IsEnum Then
            Throw New ArgumentException("Type is not an enumeration.")
        End If

        Dim items As New SortedList(Of String, Integer)
        Dim enumValues As Integer() = [Enum].GetValues(t)
        Dim enumNames As String() = [Enum].GetNames(t)

        For i As Integer = 0 To enumValues.GetUpperBound(0)
            items.Add(enumNames(i), enumValues(i))
        Next

        Return items

    End Function

You can use a extension method

 public static void EnumForComboBox(this ComboBox comboBox, Type enumType)
 {
     var memInfo = enumType.GetMembers().Where(a => a.MemberType == MemberTypes.Field).ToList();
     comboBox.Items.Clear();
     foreach (var member in memInfo)
     {
         var myAttributes = member.GetCustomAttribute(typeof(DescriptionAttribute), false);
         var description = (DescriptionAttribute)myAttributes;
         if (description != null)
         {
             if (!string.IsNullOrEmpty(description.Description))
             {
                 comboBox.Items.Add(description.Description);
                 comboBox.SelectedIndex = 0;
                 comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
             }
         }   
     }
 }

How to use ... Declare enum

using System.ComponentModel;

public enum CalculationType
{
    [Desciption("LoaderGroup")]
    LoaderGroup,
    [Description("LadingValue")]
    LadingValue,
    [Description("PerBill")]
    PerBill
}

This method show description in Combo box items

combobox1.EnumForComboBox(typeof(CalculationType));

You could use the "FindString.." functions:

Public Class Form1
    Public Enum Test
        pete
        jack
        fran
        bill
    End Enum
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ComboBox1.DataSource = [Enum].GetValues(GetType(Test))
        ComboBox1.SelectedIndex = ComboBox1.FindStringExact("jack")
        ComboBox1.SelectedIndex = ComboBox1.FindStringExact(Test.jack.ToString())
        ComboBox1.SelectedIndex = ComboBox1.FindStringExact([Enum].GetName(GetType(Test), Test.jack))
        ComboBox1.SelectedItem = Test.bill
    End Sub
End Class
comboBox1.SelectedItem = MyEnum.Something;

should work just fine ... How can you tell that SelectedItem is null?

You can use a list of KeyValuePair values as the datasource for the combobox. You will need a helper method where you can specify the enum type and it returns IEnumerable> where int is the value of enum and string is the name of the enum value. In your combobox, set, DisplayMember property to 'Key' and ValueMember property to 'Value'. Value and Key are public properties of KeyValuePair structure. Then when you set SelectedItem property to an enum value like you are doing, it should work.

At the moment I am using the Items property rather than the DataSource, it means I have to call Add for each enum value, but its a small enum, and its temporary code anyway.

Then I can just do the Convert.ToInt32 on the value and set it with SelectedIndex.

Temporary solution, but YAGNI for now.

Cheers for the ideas, I will probably use them when I do the proper version after getting a round of customer feedback.

Generic method for setting a enum as datasource for drop downs

Display would be name. Selected value would be Enum itself

public IList<KeyValuePair<string, T>> GetDataSourceFromEnum<T>() where T : struct,IConvertible
    {
        IList<KeyValuePair<string, T>> list = new BindingList<KeyValuePair<string, T>>();
        foreach (string value in Enum.GetNames(typeof(T)))
        {
            list.Add(new KeyValuePair<string, T>(value, (T)Enum.Parse(typeof(T), value)));
        }
        return list;
    }

only use casting this way:

if((YouEnum)ComboBoxControl.SelectedItem == YouEnum.Español)
{
   //TODO: type you code here
}
Related