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)
5 Answers 5
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
{
Description = (Attribute.GetCustomAttribute(value.GetType().GetField(value.ToString()), typeof(DescriptionAttribute)) as DescriptionAttribute)?.Description ?? value.ToString(),
value
})
.OrderBy(item => item.value)
.ToList();
cbo.DisplayMember = "Description";
cbo.ValueMember = "value";
}
-
-
\$\begingroup\$ @Bobson I just commented on your code bits with one of my own. \$\endgroup\$Jesse C. Slicer– Jesse C. Slicer2014年01月13日 22:55:05 +00:00Commented 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 initialEnumLabel
that I linked. I do like your modifications, though. \$\endgroup\$Bobson– Bobson2014年01月13日 23:00:05 +00:00Commented Jan 13, 2014 at 23:00 -
1\$\begingroup\$ Cool, heh. I hope whomever wrote the linked code doesn't take any offense. :) \$\endgroup\$Jesse C. Slicer– Jesse C. Slicer2014年01月13日 23:07:21 +00:00Commented 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– Kirsten2014年01月13日 23:15:35 +00:00Commented Jan 13, 2014 at 23:15
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 winforms 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.
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";
-
1\$\begingroup\$ How do I create enumList? I cant use
Enum.GetValues(typeof(HemType));
because the description is different to HemType.ToString() \$\endgroup\$Kirsten– Kirsten2014年01月13日 18:33:46 +00:00Commented Jan 13, 2014 at 18:33
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;
-
\$\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– nhgrif2015年02月27日 23:24:14 +00:00Commented Feb 27, 2015 at 23:24
-
\$\begingroup\$ Could you explain why you made these changes a little more? \$\endgroup\$user34073– user340732015年02月27日 23:25:43 +00:00Commented Feb 27, 2015 at 23:25
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;
ObjectDataProvider
to get all enum values in XAML and then bind it toItemsSource
. Here is a full example. \$\endgroup\$