Friendly-Format Enum for use in ComboBoxes, CheckedListBoxes, etc
Asked Answered
A

1

0

Requirement

I want to select values from an enum in C# using a ComboBox or select bitmasks (for enums 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.

Antigua answered 9/9, 2014 at 19:4 Comment(0)
A
0

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.

Antigua answered 9/9, 2014 at 19:4 Comment(2)
If you are trying to wrap an enum it is very strange that you are using an extension method for the ToFriendlyString that logic should be defined in your wrapper to keep it a wrapper.Minton
@Cubicle.Jockey: Not really. This way you can call ToStringFriendly() on any Enum at any time yourself, without first turning it into an EnumWrapper. I only created EnumWrapper to make ToStringFriendly() the default that combo boxes and such use.Antigua

© 2022 - 2024 — McMap. All rights reserved.