TRS
TRS

Reputation: 2097

Binding enum descriptions with combobox MVVM

Hi Iam binding my combobox with enum using a dependency object.I searched and found this solution very elegant for MVVM,

my xaml is

   <ComboBox SelectedItem="{Binding Color,Mode=TwoWay}" 
                 l:EnumHelper.Enum="{x:Type l:MyEnum }"></ComboBox>

and my dependency object is

  public class EnumHelper : DependencyObject
{
    public static Type GetEnum(DependencyObject obj)
    {
        return (Type)obj.GetValue(EnumProperty);
    }

    public static void SetEnum(DependencyObject obj, string value)
    {
        obj.SetValue(EnumProperty, value);
    }

    // Using a DependencyProperty as the backing store for Enum.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EnumProperty =
        DependencyProperty.RegisterAttached("Enum", typeof(Type), typeof(EnumHelper), new PropertyMetadata(null, OnEnumChanged));

    private static void OnEnumChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as ItemsControl;

        if (control != null)
        {
            if (e.NewValue != null)
            {
                var _enum = Enum.GetValues(e.NewValue as Type);
                control.ItemsSource = _enum;
            }
        }
    }


}

I want to know if I have to read enum descriptions and convert them back using dependency object,how can I extend this helper class.

Upvotes: 1

Views: 5258

Answers (2)

jbarker
jbarker

Reputation: 23

To reduce repetitive code I use a custom control:

Below is the usage of the custom EnumComboBox control:

<myCustomControls:EnumComboBox 
EnumType="{x:Type myEnums:MyEnumType}" 
SelectedEnumValue="{Binding MyBindingProperty}"/>

EnumComboBox just inherits from a ComboBox like so: (EnumComboBox.xaml)

<ComboBox x:Class="MyProject.MyCustomControls.EnumComboBox" />

EnumComboBox.xaml.cs:

public partial class EnumComboBox
{
    public EnumComboBox()
    {
        InitializeComponent();
    }

    public Type EnumType
    {
        get { return (Type)GetValue(EnumTypeProperty); }
        set { SetValue(EnumTypeProperty, value); }
    }

    public Enum SelectedEnumValue
    {
        get { return (Enum)GetValue(SelectedEnumValueProperty); }
        set { SetValue(SelectedEnumValueProperty, value); }
    }

    public static readonly DependencyProperty EnumTypeProperty =
        DependencyProperty.Register("EnumType", typeof(Type), typeof(EnumComboBox), new UIPropertyMetadata(null));
    public static readonly DependencyProperty SelectedEnumValueProperty =
        DependencyProperty.Register("SelectedEnumValue", typeof(Enum), typeof(EnumComboBox), new UIPropertyMetadata(null));

    private readonly Dictionary<string, Enum> _conversionDictionary = new Dictionary<string, Enum>();

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        if (e.Property == EnumTypeProperty)
        {
            foreach (var en in Enum.GetValues(EnumType))
            {
                var descr = Description((Enum)en);
                _conversionDictionary.Add(descr, (Enum)en);
            }
            ItemsSource = _conversionDictionary.Keys.OrderBy(x => x);
        }
        else if (e.Property == SelectedItemProperty)
        {
            SelectedEnumValue = _conversionDictionary[e.NewValue.ToString()];
        }
        else if (e.Property == SelectedEnumValueProperty)
        {
            SetValue(SelectedItemProperty, Description((Enum)e.NewValue));
        }
        base.OnPropertyChanged(e);
    }
    public static string Description(Enum value)
    {
        if (value == null)
            return null;
        var enumType = value.GetType();
        var field = enumType.GetField(value.ToString());
        if (field == null)
            return null;
        var attr = field.GetCustomAttributes(typeof(DescriptionAttribute), true)
                    .Cast<DescriptionAttribute>()
                    .FirstOrDefault();
        return attr == null ? value.ToString() : attr.Description;
    }
}

This is just my preferred way and wanted to share it in case anyone else wants to use it. Especially since it took a while to figure out :)

Upvotes: 1

max
max

Reputation: 34407

I use slightly different approach with MarkupExtension instead of attached property:

public sealed class EnumValues : MarkupExtension
{
    private readonly Type _enumType;

    public EnumValues(Type enumType)
    {
        _enumType = enumType;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return Enum.GetValues(_enumType);
    }
}

I find this more elegant as it is shorter and I can write ItemsSource="{l:EnumValues {l:MyEnum}}".

As for enum value descriptions, i use a converter:

public sealed class EnumValueToDecriptionConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if(value == null)
        {
            return null;
        }
        var type = value.GetType();
        if(!type.IsEnum)
        {
            return null;
        }
        var field = type.GetField(value.ToString());
        var attr = field.GetCustomAttributes(typeof(DescriptionAttribute), true)
                        .Cast<DescriptionAttribute>()
                        .FirstOrDefault();
        if(attr != null)
        {
            return attr.Description;
        }
        else
        {
            return field.Name;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    #endregion
}

So finally you can write this in XAML:

<ComboBox SelectedItem="{Binding Color, Mode=TwoWay}" 
          ItemsSource="{l:EnumValues {x:Type l:MyEnum}}">
    <FrameworkElement.Resources>
        <l:EnumValueToDecriptionConverter x:Key="EnumValueToDecriptionConverter" />
    </FrameworkElement.Resources>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Mode=OneTime,
                       Converter={StaticResource EnumValueToDecriptionConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

You can define this DataTemplate as a resource at application level if you need it more than once.

Upvotes: 4

Related Questions