This solution - which was not created by me - allows you to associate localized strings with enum values. So assuming we have an enum defined as
[EnumResourceManagerForLabels(typeof(Resources))] public enum Operator { EqualTo = 0, GreaterThan = 1, LessThan = -1 }
Step 1: Add a Windows resource (.resx) file called in this case Resources.resx
Step 2: Add string resources to the file of the form EnumTypeName_EnumName for each enum value, e.g:
Operator_EqualTo = Operator_GreaterThan > Operator_LessThan <
Now we need the EnumResourceManagerForLabels attribute which is used by our converter so it know which resource file to use (see usage above):
[AttributeUsage(AttributeTargets.Enum)] public sealed class EnumResourceManagerForLabelsAttribute : Attribute { private readonly Type m_resourceManagerType; public EnumResourceManagerForLabelsAttribute(Type resourceManagerType) { m_resourceManagerType = resourceManagerType; } public Type ResourceManagerType { get { return m_resourceManagerType; } } }
Now we need the converter:
/// <summary> /// An <see cref="IValueConverter"/> converter that converts between strings and enum types. /// </summary> public class EnumLabelConverter : IValueConverter { private readonly Dictionary<object, string> m_labelsByValue = new Dictionary<object, string>(); private readonly SortedDictionary<string, object> m_valuesByLabel = new SortedDictionary<string, object>(StringComparer.Ordinal); private Type m_enumType; private bool m_labelsAreUnique; private bool m_sortDisplayNamesAlphanumerically = true; public Type EnumType { get { return m_enumType; } set { if (value == null) { throw new ArgumentNullException("value"); } if (m_enumType != value) { m_valuesByLabel.Clear(); m_labelsByValue.Clear(); m_labelsAreUnique = true; m_enumType = value; if (m_enumType.IsEnum) { const bool lookAtInheritedAttributes = false; // Ignored. var enumResourceManagerAttribute = m_enumType.GetCustomAttributes(typeof (EnumResourceManagerForLabelsAttribute), lookAtInheritedAttributes).FirstOrDefault() as EnumResourceManagerForLabelsAttribute; ResourceManager manager; if (enumResourceManagerAttribute != null) { manager = new ResourceManager(enumResourceManagerAttribute.ResourceManagerType); } else { // We use the invariant culture for detailing exceptions caused by programmer error. throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The enum '{0}' does not have a '{1}' attribute.", m_enumType.FullName, typeof (EnumResourceManagerForLabelsAttribute). FullName), "value"); } // For each field, retrieve the label from the resource manager. foreach (FieldInfo fieldInfo in m_enumType.GetFields(BindingFlags.Public | BindingFlags.Static)) { // We use the invariant culture to compute the string to use as the key to the resource. string resourceKey = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", m_enumType.Name, fieldInfo.Name); string description = manager.GetString(resourceKey); if (string.IsNullOrEmpty(description)) { // We use the invariant culture for detailing exceptions caused by programmer error. throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, "The label for the field {0} of the enum {1} is either null, empty, or the string resource with key {2} is absent from the {3} resource file.", fieldInfo.Name, m_enumType.FullName, resourceKey, manager.BaseName)); } object fieldValue = fieldInfo.GetValue(null); if (m_valuesByLabel.ContainsKey(description)) { // We already have an entry with that label so we cannot provide ConvertBack() // functionality because of the ambiguity. m_labelsAreUnique = false; } else { m_valuesByLabel.Add(description, fieldValue); } m_labelsByValue.Add(fieldValue, description); } } else { // We use the invariant culture for detailing exceptions caused by programmer error. throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The type '{0}' is not an enum type.", m_enumType.Name), "value"); } } } } /// <summary> /// Gets or sets whether the <see cref="DisplayNames"/> are to be sorted alphanumerically. /// </summary> /// <value><b>true</b> if the <see cref="DisplayNames"/> are to be sorted alphanumerically; otherwise, <b>false</b>.</value> public bool SortDisplayNamesAlphanumerically { get { return m_sortDisplayNamesAlphanumerically; } set { m_sortDisplayNamesAlphanumerically = value; } } /// <summary> /// Gets the display names (labels) for the fields of the associated enum type. /// </summary> /// <value>The display names.</value> public IEnumerable<string> DisplayNames { get { return (SortDisplayNamesAlphanumerically) ? m_valuesByLabel.Keys : m_labelsByValue.Values as IEnumerable<string>; } } #region IValueConverter Members /// <summary> /// Converts the enum value into a string. /// </summary> /// <param name="value">The value.</param> /// <param name="targetType">Type of the target.</param> /// <param name="parameter">The parameter.</param> /// <param name="culture">The culture.</param> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // We intentionally do not assert anything about 'value', so that we fail gracefully if the binding was not hooked up correctly. // (this may be the case when first loading some UI). object result = DependencyProperty.UnsetValue; if (value != null) { // See if we have been given a single value or a collection of values. var values = value as IEnumerable; if (values != null) { var labels = new List<string>(); foreach (object item in values) { string labelString; if (m_labelsByValue.TryGetValue(item, out labelString)) { labels.Add(labelString); } else { throw new NotSupportedException(); } } result = labels; } else { string labelString; result = m_labelsByValue.TryGetValue(value, out labelString) ? labelString : DependencyProperty.UnsetValue; } } return result; } /// <summary> /// Converts the string back into an enum of the appropriate type. /// </summary> /// <param name="value">The value.</param> /// <param name="targetType">Type of the target.</param> /// <param name="parameter">The parameter.</param> /// <param name="culture">The culture.</param> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (!m_labelsAreUnique) { throw new InvalidOperationException( GetType().Name + ".ConvertBack() requires that enum labels are unique to avoid ambiguity."); } // We intentionally do not assert anything about 'value', so that we fail gracefully if the binding was not hooked up correctly, // (this may be the case when first loading some UI). object enumValue; var labelString = value as string; if (!string.IsNullOrEmpty(labelString)) { if (!m_valuesByLabel.TryGetValue(labelString, out enumValue)) { // The value for the label could not be found. enumValue = DependencyProperty.UnsetValue; } } else { // The value we were passed was a null or empty string, or not a string. enumValue = DependencyProperty.UnsetValue; } return enumValue; } #endregion }
and finally an example of usage:
<Grid> <Grid.Resources> <Converters:EnumLabelConverter x:Key="OperatorConverter" EnumType="{x:Type ViewModel:Operator}" /> </Grid.Resources> <StackPanel> <!-- Bind text block to 'GreaterThan' --> <TextBlock Text="{Binding Source={x:Static ViewModel:Operator.GreaterThan}, Converter={StaticResource OperatorConverter}}"/> <!-- Bind text block to datacontext --> <TextBlock Text="{Binding SelectedOperator, Converter={StaticResource OperatorConverter}}"/> <!-- Bind combo box to localized enums and bind to datacontext --> <ComboBox SelectedValue="{Binding SelectedOperator, Converter={StaticResource OperatorConverter}}" ItemsSource="{Binding Source={StaticResource OperatorConverter}, Path=DisplayNames}"/> <!-- Bind combo box to localized enums and select 'GreaterThan' --> <ComboBox SelectedValue="{Binding Source={x:Static ViewModel:Operator.GreaterThan}, Converter={StaticResource OperatorConverter}, Mode=OneWay}" ItemsSource="{Binding Source={StaticResource OperatorConverter}, Path=DisplayNames}"/> </StackPanel> </Grid>
Where the view model (using MVVM light) is:
public class EnumBindingSampleViewModel : ViewModelBase { private Operator _selectedOperator; public Operator SelectedOperator { get { return _selectedOperator; } set { Set(()=>SelectedOperator, ref _selectedOperator, value); } } }