14
\$\begingroup\$

I have the following code to load an enumeration into a bound combo box, but am not quite happy with it.

I would prefer to store the actual enum value in the combo box and bind directly to it. However, I can't quite figure out how to do so.

 public enum HemEnum
    {
        HemNone = -1,
        Hemsew = 0,
        HemWeld = 1,
        Hemdoublefold = 2
    }


 public static void LoadHemCombo(ComboBox cbo)
    {
    var values = from Enum e in Enum.GetValues(typeof(HemEnum))
                     select new { ID = e, Name = e.ToString() };

          foreach (var value in values)
    {
        var s = GetHemTypeDescription((HemEnum)value.ID );
        cbo.Items.Add(s);
    }
    }

  public static string GetHemTypeDescription(HemEnum hemType)
    {
        string s = null;
        switch (hemType)
        {
            case HemEnum.HemNone:
                s = "none";
                break;
            case HemEnum.Hemsew:
                s = "sewn";
                break;
            case HemEnum.HemWeld:
                s = "welded";
                break;
            case HemEnum.hemdoublefold:
                s = "double folded";
                break;
            default:
                s = "not known";
                break;
        }
        return s;
    }

Inside the form load event I load the combo and bind to it.

 LoadHemCombo(cboHem)
 cboHem.DataBindings.Add("Text", myBindingSource, "HemTypeDescription");

 class myObject
 {
    public string HemTypeDescription
    {
        get
        {
            return GetHemTypeDescription(this.HemType);

        }
        set
        {
            this.HemType = GetHemTypeFromDescription(value);
        }
  }

public static vHemEnum GetHemTypeFromDescription(string description)
    {
        int r =0;
        for (var i = (int)HemEnum.HemNone; i <= (int)HemEnum.Hemdoublefold; i++)
        {
            var s = GetHemTypeDescription((HemEnum)i);
            if (description == s)
            {
                r  = i;
                break;
            }
        }
        return (HemEnum)r;
    }
 }

in the designer I set

myBindingSource.DataSource = myObject  

and before loading the form I create an instance of myObject and add it to myBindingSource using

myBindingSource.Add(myObject)
\$\endgroup\$
3
  • \$\begingroup\$ I've got a custom class devoted to solving this over here. All the advice below is good advice, but this class also addresses pulling the data back out as the right type. \$\endgroup\$
    – Bobson
    Jan 13, 2014 at 22:32
  • \$\begingroup\$ Thanks every one. I also made it generic. So now I just have 'public void LoadCombo<T>(ComboBox cbo)' \$\endgroup\$
    – Kirsten
    Jan 13, 2014 at 23:31
  • \$\begingroup\$ You can use ObjectDataProvider to get all enum values in XAML and then bind it to ItemsSource. Here is a full example. \$\endgroup\$
    – druss
    Jan 27, 2015 at 15:09

5 Answers 5

14
\$\begingroup\$

Here may be an decent way of doing what you want:

public enum HemEnum
{
    [Description("none")]
    HemNone = -1,
    [Description("sewn")]
    Hemsew = 0,
    [Description("welded")]
    HemWeld = 1,
    [Description("double folded")]
    Hemdoublefold = 2
}


public static void LoadHemCombo(ComboBox cbo)
{
    cbo.DataSource = Enum.GetValues(typeof(HemEnum))
        .Cast<Enum>()
        .Select(value => new
        {
            (Attribute.GetCustomAttribute(value.GetType().GetField(value.ToString()), typeof(DescriptionAttribute)) as DescriptionAttribute).Description,
            value
        })
        .OrderBy(item => item.value)
        .ToList();
    cbo.DisplayMember = "Description";
    cbo.ValueMember = "value";
}

Do note, this method requires a [Description] on every enum member. If one isn't there, you'll get a NullReferenceException. This requirement keeps the code simple by not needing a nullity check.

\$\endgroup\$
6
  • \$\begingroup\$ This is very similar to what I use, but I've extended it a bit further so that the [Description] attribute doesn't have to be there. See the code here for an example. \$\endgroup\$
    – Bobson
    Jan 13, 2014 at 22:27
  • \$\begingroup\$ @Bobson I just commented on your code bits with one of my own. \$\endgroup\$ Jan 13, 2014 at 22:55
  • \$\begingroup\$ Sorry, I phrased that rather misleadingly (too much editing). My code is a slight modification of the linked code, which itself supported missing [Description] attributes. I didn't intend to take credit for writing the initial EnumLabel that I linked. I do like your modifications, though. \$\endgroup\$
    – Bobson
    Jan 13, 2014 at 23:00
  • 1
    \$\begingroup\$ Cool, heh. I hope whomever wrote the linked code doesn't take any offense. :) \$\endgroup\$ Jan 13, 2014 at 23:07
  • \$\begingroup\$ Wonderful. I changed the binding to ' cboHem.DataBindings.Add("SelectedValue", myBindingSource, 'HemType") so I could use it; \$\endgroup\$
    – Kirsten
    Jan 13, 2014 at 23:15
13
\$\begingroup\$

Naming

HemEnum, or any enum name that ends with the word Enum, is a bad name for an enum. Enum types should not contain the word "enum" in their names.

Similarly, HemEnum values should not contain "Hem" in their names either. Your HemEnum should therefore read something like this:

public enum HemType
{
    None = -1,
    Sewn = 0,
    Weld = 1,
    DoubleFold = 2
}

myObject is a bad name for a class - should be MyObject. Kidding (although not really - types should be named following a PascalCasing convention). Anything that ends with "Object" should be banned from being a class name. Note that myObject doesn't compile as provided. (where's this.HemType?)

The pseudo-Hungarian notation is ok in for naming controls (i.e. the "cbo" prefix for ComboBoxes, "txt" for TextBoxes, etc.) - had you been using current technology (WPF) I would have strongly advised against such naming though.

Nitpicks

You seem to make static anything that can be made static. Don't. Just because a method doesn't use instance members now doesn't mean it never will, and changing a public static void method to be public void, is a breaking change.

vHemEnum doesn't seem to be a type defined anywhere. Typo?

I'll assume the out-of-whack indentation is a Copy+Paste glitch.

Binding?

You say you want databinding, and yet you're doing this:

foreach (var value in values)
{
    var s = GetHemTypeDescription((HemEnum)value.ID );
    cbo.Items.Add(s);
}

Create a type that exposes DisplayValue and EnumValue properties, and use @DanLyons' or @JesseCSlicer's answer to bind your combobox items.

Captions

A more extensible way (vs. attributes) of providing captions for your enums, would be to use a resource file (.resx) - call the strings per the enum's names, and then retrieve the caption from the resource strings:

var description = resx.ResourceManager.GetString(hemType.ToString());

Bottom line, I've seen worse WinForms code (and WPF for that matter), but it could be improved.

\$\endgroup\$
3
\$\begingroup\$

The easiest way to accomplish what you want is to create a list of the possible enum values and data bind that list to the combo box. You can do this through the designer (under Data) or with the following code:

cboHem.DataSource = enumList;

With an enum, it should automatically use the individual enum values as the selected value and display the enum values' .ToString result in the control.

If you use localization strings, it's a little (but not much) more complicated. Instead of binding enum values directly, you will want to bind a list of objects with the enum value and your localized string representation for the value, and then set the DisplayMember and ValueMember properties to the appropriate fields on your bound objects.

Once again, that can be done through the designer (under Data again) or through code, as follows:

cboHem.DisplayMember = "DisplayValue";
cboHem.ValueMember = "EnumValue";
\$\endgroup\$
1
  • 1
    \$\begingroup\$ How do I create enumList? I cant use Enum.GetValues(typeof(HemType)); because the description is different to HemType.ToString() \$\endgroup\$
    – Kirsten
    Jan 13, 2014 at 18:33
1
\$\begingroup\$

I did a quick mock up using the following and it works well.

Enumerated Values

 public enum HemEnum
 {
   [Description("None")]
   None = -1,
   [Description("Item will be sewn")]
   Sew = 0,
   [Description("Item will be welded")]
   Weld = 1,
   [Description("Item will be double folded")]
   Doublefold = 2
 }

A name value binder used to populate a binding source, this is the class that will be bound as an item to the combobox. Mapping the cboEnum.ValueMember to the NameValueBinder.Value etc..

public class NameValueBinder
{
  public NameValueBinder()
  {
  }

  public NameValueBinder(object value, string name)
  {
    this.Value = value;
    this.Name = name;
  }

  public object Value { get; set; }
  public string Name { get; set; }
}

Method in your form or class to pull the items into a list. This function takes an enum type as the parameter and retrieves a list of all enumeration values in the enum and converts each one to a NameValueBinder item and returns the list of all items. The type being passed as a parameter allows one function to retrieve a bindable list for any enum in the application, no need to write one function per enumeration.

public List<NameValueBinder> GetValues(Type type)
{
  List<NameValueBinder> binders = new List<NameValueBinder>();

  if (type.BaseType != typeof(Enum))
    return binders;

  var items = Enum.GetValues(type);

   foreach (var item in items)
   {
     binders.Add((item as Enum).ToListItem());
   }

    return binders;
  }

Extension class to convert from enum to binder item. The first function ToListItem converts the enum value (i.e. HemEnum.Weld) to a NameValueBinder class by mapping the enum value to the Value property and the DescriptionAttribute value the name property. The binder will be added to a list or collection for databainding to the ui element. The second function retrieves the value of the DescriptionAttribute for the enum value. If the enum value does not have an associated DescriptionAttribute, the name of the enum is returned in it's place.

public static class EnumExtensions
{
  public static NameValueBinder ToListItem(this Enum value)
  {
    string description = value.GetDescription();
    return new NameValueBinder(value, description);
  }

  public static string GetDescription(this Enum enumVal)
  {
    var type = enumVal.GetType();
    var memInfo = type.GetMember(enumVal.ToString());
    var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

    foreach (var attribute in attributes)
    {
      if(attribute .GetType() == typeof(DescriptionAttribute))
        return (attribute as DescriptionAttribute).Description;
    }

  // no description attribute found, just return the name
  return enumVal.ToString();      }
}

Usage

// create a binding source and populates it with the enum values/descriptions
BindingSource cboLookupBinding = new BindingSource();
cboLookupBinding.DataSource = typeof(NameValueBinder);
cboLookupBinding.DataSource = GetValues(typeof(HemEnum));

// bind the combobox to the bindingsource
cboHem.ValueMember = "Value";
cboHem.DisplayMember = "Name";
cboHem.DataSource = cboLookupBinding;
\$\endgroup\$
2
  • \$\begingroup\$ Good code review answers typically have more plain-English than actual source code. There's nothing wrong with posting code to show what you mean, but we really like good explanations of why your code is better and what mistakes were made and what warrants the original code being replaced with your code \$\endgroup\$
    – nhgrif
    Feb 27, 2015 at 23:24
  • \$\begingroup\$ Could you explain why you made these changes a little more? \$\endgroup\$
    – user34073
    Feb 27, 2015 at 23:25
0
\$\begingroup\$

All of the answers I found on the internet seem pretty complex. I found that you can get an array from an enum and convert the array to a list, which can be used as a datasource for your combobox. It's extremely simple and seems to be working in my project.

public enum Status
{
    Open = 1,
    Closed,
    OnHold
}

List<Status> lstStatus = Enum.GetValues(typeof(Status)).OfType<Status>().ToList();
ddlStatus.DataSource = lstStatus;
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.