Question

I'm trying to simplify at maximum the binding of Enums to ComboBoxes.

Among multiple solutions (ObjectDataProvider, Converter...) I opted for the following MarkupExtension :

public class EnumSource : MarkupExtension
{

    public class EnumMember
    {
        public string Display { get; set; }
        public object Value { get; set; }

        public override string ToString()
        {
            return Display;
        }
    }


    private readonly Type EnumType;

    public EnumSource(Type type)
    {
        EnumType = type;
    }


    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var enumValues = Enum.GetValues(EnumType);
        return (
          from object enumValue in enumValues
          select new EnumMember
          {
              Value = enumValue,
              Display = GetDescription(enumValue)
          }).ToArray();
    }


    private string GetDescription(object enumValue)
    {
        var descriptionAttribute = EnumType
                                      .GetField(enumValue.ToString())
                                      .GetCustomAttributes(typeof(DescriptionAttribute), false)
                                      .FirstOrDefault() as DescriptionAttribute;

        return (descriptionAttribute != null) ? descriptionAttribute.Description : enumValue.ToString();
    }

}


<ComboBox ItemsSource="{Binding Source={my:EnumSource {x:Type my:Options}}}" SelectedValue="{Binding Path=CurrentOption}" SelectedValuePath="Value" />

You can notice I managed to get rid of the DisplayMemberPath="Display" by adding a ToString() method in the EnumMember class.

Is it possible to replace the SelectedValuePath="Value" attribute with something like a class operator (inside EnumMember) or a similar thing?

Thanks!

Was it helpful?

Solution

There are lot of approaches to achieve that:

Approach 1

The ProvideValue method of a markup extension takes a parameter of type IServiceProvider, which provides, among others, a IProvideValueTarget service. This interface exposes property TargetObject which allow to retrieve the target object (combobox in your case).

You can set SelectedValuePath on that like this:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
    if (target != null && target.TargetObject is ComboBox)
    {
        ((ComboBox)target.TargetObject).SelectedValuePath = "Value";
    }

    var enumValues = Enum.GetValues(EnumType);
    return (
       from object enumValue in enumValues
       select new EnumMember
       {
          Value = enumValue,
          Display = GetDescription(enumValue)
       }).ToArray();
}

Approach 2

In case all comboboxes in your app will be binded to wrapper class EnumMember, you can define it under global style of ComboBox under Application.Resources so that you don't have to duplicate it for each and every comboBox.

Even in case, you are not binding ComboBox to enum value, SelectedValuePath can be overriden per instance.

<Application.Resources>
   <Style TargetType="ComboBox">
      <Setter Property="SelectedValuePath" Value="Value"/>
   </Style>
</Application.Resources>

Approach 3

You can subclass ComboBox and OnItemsSourceChanged, set SelectedValuePath to Value in case ItemsSource is array of EnumMember class.

public class MyComboBox : ComboBox
{
    protected override void OnItemsSourceChanged(IEnumerable oldValue,
                                                 IEnumerable newValue)
    {
        if (newValue != null && 
            newValue.GetType().Equals(typeof(EnumSource.EnumMember[])))
        {
            SelectedValuePath = "Value";
        }
        base.OnItemsSourceChanged(oldValue, newValue);
    }
}

usage in XAML:

<my:MyComboBox ItemsSource="{my:EnumSource {x:Type my:Options}}"
               SelectedValue="{Binding Path=CurrentOption}"/>

On a side note sine you created own markup extension, you don't need Binding at all. You can bind ItemsSource like this:

    <ComboBox ItemsSource="{my:EnumSource {x:Type my:Options}}"
              SelectedValue="{Binding CurrentOption}"/>

OTHER TIPS

It is not possible to do that because SelectedValue extract the value of specific property from SelectedItem based on property mentioned in SelectedValuePath .Means SelectedValue and SelectedValuePath works together. However If you want to do it without mentioning SlectedValuePath , then use SelectedItem property for Binding.

Update this is a property in ViewModel

    EnumSource.EnumMember selectedItem;

    public  EnumSource.EnumMember SelectedItem
    { 
        get{return  selectedItem;}
        set { selectedItem = value; OnPropertyChanged("SelectedItem");}
    }

xaml

<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Source={local:EnumSource {x:Type local:DataType}}}" SelectedValue="{Binding Path=CurrentOption}"/>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top