2
votes

I have read lots of useful posts on the topic of the Winforms ComboBox ValueMember property, but none of them have answered my specific question. First, I will describe a simple example that works just fine, then I will describe how I want to change it, and ask how to do that (I will also offer one of my attempts).

First, the working example. Just a form with a comboBox control, and this code:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        comboBox1.DataSource = Enum.GetValues(typeof(Enum1));
        comboBox1.SelectedItem = Enum1.MultiWordValue2;
    }
}

public enum Enum1 : int
{
    Undefined,
    MultiWordValue1,
    MultiWordValue2
}

No problem, this works just fine. I run the app and the comboBox gets the appropriate items, and selects the appropriate value.

However, these enum values are ugly, and I want my users to have a better experience, so I create an extension method to give the values better a display.

public static class ExtensionMethods
{
    public static string ToDisplayString(this Enum1 me)
    {
        switch (me)
        {
            case Enum1.MultiWordValue1:
                return "Multi Word Value 1";
            case Enum1.MultiWordValue2:
                return "Multi Word Value 2";
            default:
                return string.Empty;
        }
    }
}

My question is, how best do I utilize this extension method while preserving the ability to build the comboBox items from the list of enum values, and be able to set the selected comboBox item (or value) by the enum value?

Below is my first attempt. I create a simple class to wrap the enum value and display string and set the comboBox DisplayMember and ValueMember to the properties of the new class. This partially works; the items of the combobox populate correctly with the display value, but I cannot set the SelectedValue:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        comboBox1.DisplayMember = "Display";
        comboBox1.ValueMember = "Value";
        foreach (Enum1 e in Enum.GetValues(typeof(Enum1)))
            comboBox1.Items.Add(new ValueDisplayEnum(e, e.ToDisplayString()));
        comboBox1.SelectedValue = Enum1.MultiWordValue2;
    }
}

public enum Enum1 : int
{
    Undefined,
    MultiWordValue1,
    MultiWordValue2
}

public static class ExtensionMethods
{
    public static string ToDisplayString(this Enum1 me)
    {
        switch (me)
        {
            case Enum1.MultiWordValue1:
                return "Multi Word Value 1";
            case Enum1.MultiWordValue2:
                return "Multi Word Value 2";
            default:
                return string.Empty;
        }
    }
}

public class ValueDisplayEnum
{
    public object Value { get; set; }
    public string Display { get; set; }
    private ValueDisplayEnum() { }
    public ValueDisplayEnum(object _Value, string _Display)
    {
        Value = _Value;
        Display = _Display;
    }
}

Why doesn't setting the SelectedValue this way work? And how do I accomplish a comboBox of enums with better display strings?

Aside: I thought it might be because the underlying data type of the Value property of ValueDisplayEnum is an object, and not Enum1. However, if I change the type to Enum1, the SelectedValue still doesn't set correctly.

Thanks for any help!

Edit 1: Specifying one solution based on the suggestion by Mike:

Added the following templatized method:

public static void SetEnumCombo<T>(ComboBox _ComboBox, T _Value)
{
    foreach (ValueDisplayEnum vde in _ComboBox.Items)
        if (((T)vde.Value).Equals(_Value))
            _ComboBox.SelectedItem = vde;
}

Instead of assigning SelectedValue or SelectedItem directly, called this:

foreach (Enum1 e in Enum.GetValues(typeof(Enum1)))
    comboBox1.Items.Add(new ValueDisplayEnum(e, e.ToDisplayString()));
SetEnumCombo(comboBox1, Enum1.MultiWordValue2);

And everything worked like a charm. I'm still not sure why setting by value doesn't work, but this is a very compact resolution to the problem. But not as compact as response marked as the answer!

4
Well setting the SelectedValue does not work because you have string data and want to select it with an enum value.user1064248
You may want to see this too how-do-i-override-tostring-in-c-sharp-enumsnawfal

4 Answers

4
votes

In your first attempt, you could select desired item with:

comboBox1.SelectedItem = comboBox1.Items.Cast<ValueDisplayEnum>().First(x => (Enum1)x.Value == Enum1.MultiWordValue2);
3
votes

A generic utility class that uses the Description attribute on an enum field to control the display value for an enum in a combo box.

    /// <summary>
    /// Return the contents of the enumeration as formatted for a combo box
    /// relying on the Description attribute containing the display value
    /// within the enum definition
    /// </summary>
    /// <typeparam name="T">The type of the enum being retrieved</typeparam>
    /// <returns>The collection of enum values and description fields</returns>
    public static ICollection<ComboBoxLoader<T>> GetEnumComboBox<T>()
    {
        ICollection<ComboBoxLoader<T>> result = new List<ComboBoxLoader<T>>();
        foreach (T e in Enum.GetValues(typeof(T)))
        {
            ComboBoxLoader<T> value = new ComboBoxLoader<T>();
            try
            {
                value.Display = GetDescription(e);
            }
            catch (NullReferenceException)
            {
                // This exception received when no Description attribute
                // associated with Enum members
                value.Display = e.ToString();
            }
            value.Value = e;
            result.Add(value);
        }
        return result;
    }

To use it in a data grid view on a form for example:

        DataGridViewComboBoxColumn trussLocationComboBoxColumn = trussLocationColumn as DataGridViewComboBoxColumn;
        trussLocationComboBoxColumn.DataSource = EnumUtils.GetEnumComboBox<TrussLocationCase>();
        trussLocationComboBoxColumn.DisplayMember = "Display";
        trussLocationComboBoxColumn.ValueMember = "Value";

and the descriptions on the Enum fields that are picked up for the Display field

public enum TrussLocationCase
{
    [Description("All Cases")]
    AllCases,
    [Description("1A")]
    OneA,
    [Description("1B")]
    OneB,
    [Description("2C")]

and to finish it all off, the ComboBoxLoader class

/// <summary>
/// Class to provide assistance for separation of concern
/// over contents of combo box where the
/// displayed value does not match the ToString 
/// support.
/// </summary>
/// <typeparam name="T">The type of the value the combo box supports</typeparam>
[DebuggerDisplay("ComboBoxLoader {Display} {Value.ToString()}")]
public class ComboBoxLoader<T>
{
    /// <summary>
    /// The value to display in the combo box
    /// </summary>
    public string Display { get; set; }

    /// <summary>
    /// The actual object associated with the combo box item
    /// </summary>
    public T Value { get; set; }
}

would see the combo box populated with

All Cases
1A
1B
2C
...
1
votes

I like the following solution because you add or change enumerations and their string values in one place - in the array extendedEnumerations.

public partial class Form1 : Form  
{  
    public enum MyEnum  
    {  
        EnumValue1,  
        EnumValue2,  
        EnumValue3  
    }  

    public class EnumExtension  
    {  
        public MyEnum enumValue;  
        public String enumString;  
        public EnumExtension(MyEnum enumValue, String enumString)  
        {  
            this.enumValue = enumValue;  
            this.enumString = enumString;  
        }  
        public override string ToString()  
        {  
            return enumString;  
        }  
    }  

    private EnumExtension[] extendedEnumerations =  
    {  
        new EnumExtension(MyEnum.EnumValue1, "Enum Value 1"),  
        new EnumExtension(MyEnum.EnumValue2, "Enum Value 2"),  
        new EnumExtension(MyEnum.EnumValue3, "Enum Value 3"),  
    };  

    public Form1()  
    {  
        InitializeComponent();  
        foreach (EnumExtension nextEnum in extendedEnumerations)  
            comboBox1.Items.Add(nextEnum);  
        comboBox1.SelectedIndexChanged += new EventHandler(comboBox1_SelectedIndexChanged);  

        button1.Click += new EventHandler(button1_Click);  
        button2.Click += new EventHandler(button2_Click);  
        button3.Click += new EventHandler(button3_Click);  
    }  

    void button3_Click(object sender, EventArgs e)  
    {  
        comboBox1.SelectedItem = extendedEnumerations[2];  
    }  

    void button2_Click(object sender, EventArgs e)  
    {  
        SetSelectedEnumeration(MyEnum.EnumValue2);  
    }  

    void button1_Click(object sender, EventArgs e)  
    {
        SetSelectedEnumeration(MyEnum.EnumValue1);
    }

    private void SetSelectedEnumeration(MyEnum myEnum)
    {
        foreach (EnumExtension nextEnum in comboBox1.Items)
        {
            if (nextEnum.enumValue == myEnum)
            {
                comboBox1.SelectedItem = nextEnum;
                break;
            }
        }
    }

    void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        EnumExtension selectedExtension = (EnumExtension)comboBox1.SelectedItem;
        MyEnum selectedValue = selectedExtension.enumValue;
    }
}
0
votes

Well setting the SelectedValue does not work because you have string data and want to select it with an enum value.

An easy solution would be something like this:

private static void FillCombo(ComboBox box)
        {
            List<DisplayableStatus> ds = new List<DisplayableStatus>();
            foreach (var val in Enum.GetValues(typeof(Status)))
                ds.Add(new DisplayableStatus { Status = (Status)val, DisplayText = val.ToString() + " Nice" });

            box.DataSource = ds;
            box.DisplayMember = "DisplayText";
            box.ValueMember = "Status";

            box.SelectedValue = Status.Stop;
        }

        public enum Status
        {
            Unknown,
            Start,
            Stop
        }

        public class DisplayableStatus
        {
            public Status Status { get; set; }
            public string DisplayText { get; set; }
        }

As you can see you can set a display and a value member to seperate data and view.