0
votes

I have several ComboBox controls, with DropDownStyle set to DropDownList. The items that are selected are enum values, but I want "friendly" descriptions of these to be displayed, i.e. with spaces and not camel case.

I want the combo box to be "two-way" bound to the data object: if the data object changes its property it alters the combo box, and vice versa.

I can do this easily with strings but the problem is I am binding the controls to enum properties in the objects, so I want the Items collection of the Combo Box to contain actual enums. This obviously isn't going to work.

Solution 1: textual properties

I can create extra properties in my object which are textual, and map those to the enum values. This is a bit messy though as I am including things in my business logic layer which really belong in the UI. In that business logic layer they should be enums and not strings.

Solution 2: event handlers

Another alternative is to use event handlers so that when the user changes the option it gets the selected item text and finds the appropriate enum value, then sets this in the object. This is only one way binding though.

Attempted Solution 3

public class BusinessObject
{
    private NumberCategory category;

    public NumberCategory Category
    {
        get
        {
            return category;
        }
        set
        {
            category = value;
        }
    }
}

public enum NumberCategory
{
    [Description("Negative Number")]
    NegativeNumber,

    [Description("Zero")]
    Zero,

    [Description("One")]
    One,

    [Description("Prime Number")]
    PrimeNumber,

    [Description("Composite Number")]
    CompositeNumber,
}

public class EnumDescriptionAdapter
{
    private readonly BusinessObject businessObject;

    public EnumDescriptionAdapter(BusinessObject businessObject)
    {
        this.businessObject = businessObject;
    }

    public string CategoryValue
    {
        get
        {
           //get the enum from businessObject and convert to a string
            return EnumUtils.GetDescription(businessObject.Category);
        }

        set
        {
            //get the string, convert to an enum and set it in BusinessObject
            businessObject.Category = EnumUtils.GetValueFromDescription<NumberCategory>(value); 
        }
    }
}

public static class EnumUtils 
{

    public static T GetValueFromDescription<T>(string description)
    {
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();
        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attribute != null)
            {
                if (attribute.Description == description)
                    return (T)field.GetValue(null);
            }
            else
            {
                if (field.Name == description)
                    return (T)field.GetValue(null);
            }
        }
        throw new ArgumentException("Not found.", "description");
        // or return default(T);
    }

    public static string GetDescription(Enum value)
    {
        Type type = value.GetType();
        string name = Enum.GetName(type, value);
        if (name != null)
        {
            FieldInfo field = type.GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr =
                       Attribute.GetCustomAttribute(field,
                         typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}

public partial class Form1 : Form
{
    public BusinessObject businessObject;

    public Form1()
    {
        InitializeComponent();

        string[] descriptions = Enum.GetValues(typeof(NumberCategory)).Cast<NumberCategory>().Select(e => EnumUtils.GetDescription(e)).ToArray();

        comboBox1.DataSource = descriptions;

        businessObject = new BusinessObject();
        EnumDescriptionAdapter adapter = new EnumDescriptionAdapter(businessObject);

        comboBox1.DataBindings.Add(new Binding("SelectedItem", adapter, "CategoryValue"));
    }

    private void button1_Click(object sender, EventArgs e)
    {
        businessObject.Category = NumberCategory.PrimeNumber;
    }
}

I put a button (button1) and a combo box (comboBox1) on my form. When I change the combo box selected item, it does fire the setter in EnumDescriptionAdapter.CategoryValue and change the businessObject. However, the reverse is not true: if I press the button it changes the businessObject but doesn't alter the selected item in comboBox1.

1
What I used to do is build a function that I could pass any value of a specific enum to, and then with that I would get the string-representation of the enum, and parse the string manually. (On each upper-case letter add a space before, etc.)Der Kommissar

1 Answers

3
votes

I don't know if it is more elegant but you can create a class with 2 properties, one for showing and one as value. You then populate a list of such created, one way or another, from your enums.

You get a star for recognising that decorating business layer stuff with presentation layer stuff is not considered good practice.