HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Localized Enum in C# 6

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
enumlocalizedstackoverflow

Problem

I had to jump through some hoops to get a localized enum working. I went through dozens of SO articles that never actually answered the relevant conundrum with a working solution. So I made my own:

I have two resource files: LocalizedStrings.resx and LocalizedStrings.es-MX.resx
With two corresponding lines:

GENDER_FEMALE   Female

GENDER_MALE     Male


... and ...

GENDER_FEMALE   Mujer

GENDER_MALE     Hombre


… respectively.

I have an Enum Extension:

public static class EnumExtensionMethods
{
    public static string GetDisplay(this Enum enumValue)
    {
        object[] attr = enumValue.GetType().GetField(enumValue.ToString())
            .GetCustomAttributes(typeof(DisplayAttribute), false);

        return attr.Length > 0
           ? ((DisplayAttribute)attr[0]).GetName()
           : enumValue.ToString();
    }
}


My actual Enum:

public enum Gender
{
    [Display(ResourceType = typeof(LocalizedStrings), Name = "GENDER_MALE")]
    Male = 1,
    [Display(ResourceType = typeof(LocalizedStrings), Name = "GENDER_FEMALE")]
    Female = 0
}


I load the values into a Winforms ComboBox’s items, (I could not get binding it to an entity to work) with the OnLoad event:

foreach (Gender value in Enum.GetValues(typeof(Gender)))
{
    GenderComboBox.Items.Add(new KeyValuePair(value.GetDisplay(), value));
}

GenderComboBox.DisplayMember = "Key";
GenderComboBox.ValueMember = "Value";
GenderComboBox.SelectedIndex = 1;


Finally, I have to set the entity value with this event:

private void GenderComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
    if (!GenderComboBox.Focused)
        return;

    if (GenderComboBox.SelectedIndex == -1)
        return;

    KeyValuePair gender = (KeyValuePair) GenderComboBox.SelectedItem;

    Person person = (Person)PersonBindingSource.Current;

    person.Gender = gender.Value;
}


All of the previous code just to get a Localized Enum. Is there not a better way?

Solution

You really shouldn't mix event and data-binding together.

The proper way of updating value from UI, would be using ComboBox.DataBindings.Add(Binding). And, not patching it via ComboBox.SelectedSomethingChanged.

Here is how I would do it :

// mock model
var person = new Person { Gender = Gender.Female };;

// mock data source
var personBindingSource = new BindingSource();
personBindingSource.DataSource = new List { person };

// initialize
var combo = new ComboBox();
// you may want to set DropDownStyle to ComboBoxStyle.DropDownList
// from the designer to disable text input.
//
// choose one of the two below :
//    combo.DisplayMember = "Value";
//    combo.ValueMember = "Key";
//    combo.DataSource = EnumHelper.GetEnumDefinitions();
combo.BindTo();

// databind
combo.DataBindings.Add(new Binding(
    nameof(combo.SelectedValue), personBindingSource, nameof(person.Gender), false, DataSourceUpdateMode.OnPropertyChanged));


public static class EnumHelper
{
    public static IList GetEnumDefinitions()
        where TEnum : struct, IConvertible
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
            throw new ArgumentException($"'{type}' is not an enum type.");

        return type
            .GetFields(BindingFlags.Public | BindingFlags.Static)
            .ToDictionary(x => (TEnum)x.GetValue(null), x =>
                x.GetCustomAttribute()?.GetName() ?? x.GetValue(null).ToString()
            )
            .ToList();
    }

    public static void BindTo(this ComboBox combo)
        where TEnum : struct, IConvertible
    {
        combo.DisplayMember = "Value";
        combo.ValueMember = "Key";
        combo.DataSource = EnumHelper.GetEnumDefinitions();
    }
}

Code Snippets

// mock model
var person = new Person { Gender = Gender.Female };;

// mock data source
var personBindingSource = new BindingSource();
personBindingSource.DataSource = new List<Person> { person };

// initialize
var combo = new ComboBox();
// you may want to set DropDownStyle to ComboBoxStyle.DropDownList
// from the designer to disable text input.
//
// choose one of the two below :
//    combo.DisplayMember = "Value";
//    combo.ValueMember = "Key";
//    combo.DataSource = EnumHelper.GetEnumDefinitions<Gender>();
combo.BindTo<Gender>();

// databind
combo.DataBindings.Add(new Binding(
    nameof(combo.SelectedValue), personBindingSource, nameof(person.Gender), false, DataSourceUpdateMode.OnPropertyChanged));
public static class EnumHelper
{
    public static IList GetEnumDefinitions<TEnum>()
        where TEnum : struct, IConvertible
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
            throw new ArgumentException($"'{type}' is not an enum type.");

        return type
            .GetFields(BindingFlags.Public | BindingFlags.Static)
            .ToDictionary(x => (TEnum)x.GetValue(null), x =>
                x.GetCustomAttribute<DisplayAttribute>()?.GetName() ?? x.GetValue(null).ToString()
            )
            .ToList();
    }

    public static void BindTo<TEnum>(this ComboBox combo)
        where TEnum : struct, IConvertible
    {
        combo.DisplayMember = "Value";
        combo.ValueMember = "Key";
        combo.DataSource = EnumHelper.GetEnumDefinitions<TEnum>();
    }
}

Context

StackExchange Code Review Q#139379, answer score: 3

Revisions (0)

No revisions yet.