Requirement
I want to select values from an enum
in C# using a ComboBox
or select bitmasks (for enum
s with the Flags
attribute) with a CheckedListBox
. I want a way to add the values to the controls as selectable items, and cleanly tell which the user has selected.
Objective 1: User-friendly
I also want the selection to be clear and pretty to the user. Currently I can already add Enum
values to a ComboBox
or a CheckedListBox
, but Enum.ToString()
will return the identifier name. Pascal Case is good enough for me, but not for my users.
Objective 2: Easy to code
I want this to be easy to code. I mean easy. I want this to be a mere afterthought to defining and/or using any enum
value.
My Solution
Now I looked around and saw several nice solutions. Some of them are better in their own ways, like if you really need to put in your own custom descriptions, or to take it one step further, support multiple languages. You can do fake enums too. But I kept looking, and none of them had quite the combination of elegance and simplicity--for my purposes--as what I wrote. See below.
The basis of my solution is a struct
that can wrap Enum
values and override ToString()
.
Enter the EnumWrapper
:
public struct EnumWrapper
{
private readonly Enum e;
public EnumWrapper(Enum e) {
this.e = e;
}
public static implicit operator Enum(EnumWrapper wrapper) {
return wrapper.e;
}
public static explicit operator EnumWrapper(Enum e) {
return new EnumWrapper(e);
}
public override string ToString() {
return e.ToStringFriendly();
}
}
The method ToStringFriendly()
is defined as an extension method on Enum
:
using System.Text.RegularExpressions;
public static class _Extensions
{
public static string ToStringFriendly(this Enum e)
{
string s = e.ToString();
// enforce a charset: letters, numbers, and underscores
s = Regex.Replace(s, "[^A-Za-z0-9_]", "");
// separate numbers from letters
s = Regex.Replace(s, "([a-zA-Z])([0-9])", "$1 $2");
// separate letters from numbers
s = Regex.Replace(s, "([0-9])([a-zA-Z])", "$1 $2");
// space lowercases before uppercase (word boundary)
s = Regex.Replace(s, "([a-z])([A-Z])", "$1 $2");
// see that the nice pretty capitalized words are spaced left
s = Regex.Replace(s, "(?!^)([^ _])([A-Z][a-z]+)", "$1 $2");
// replace double underscores with colon-space delimiter
s = Regex.Replace(s, "__", ": ");
// finally replace single underscores with hyphens
s = Regex.Replace(s, "_", "-");
return s;
}
}
Now, to add any Enum
value to a ComboBox, for example,
comboBox.Items.Add((EnumWrapper)MyEnum.SomeValue);
And to get it back out (after null-testing, of course):
MyEnum myValue = (MyEnum)(Enum)(EnumWrapper)comboBox.SelectedItem;
And that's it. Now what are the pros and cons of this approach?
The Pros:
- You can pass the enum values (almost) directly in and out of your controls. Just cast back and forth to/from
EnumWrapper
.
- This works nicely for all Pascal-cased
Enum
values with a few special cases. One example might be a value called MyValue__DescriptionOf123_ENUMValue
which would come out of ToStringFriendly()
as "My Value: Description of 123-ENUM Value".
- The mechanics (a
struct
and an extension method) can be written once and tucked out of the way. There is no additional coding in each Enum
. This means it works nicely as above for enums you didn't write, assuming Pascal case, which in .NET is a good assumption. Note: This is why I'd say this answer, however elegant, is not a better solution than mine, as it requires that you can add a TypeConverter
attribute to each Enum
.
The Cons:
- This doesn't support custom descriptions or multiple languages. For example a value like
Encoding.EBCDIC
will always come through as "EBCDIC" and does not allow you to manually type "Extended Binary Coded Decimal Interchange Code", much less other languages.
Future Work
One could add custom descriptions and multi-language support by changing ToStringFriendly()
to do a language-lookup for that value before de-Pascal-casing.
For more Regex fun, see this thread.