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