Databinding an enum property to a ComboBox in WPF

2019-01-01 04:27发布

As an example take the following code:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } }
}

I want a to databind the property ExampleProperty to a ComboBox, so that it shows the options "FooBar" and "BarFoo" and works in mode TwoWay. Optimally I want my ComboBox definition to look something like this:

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />

Currently I have handlers for the ComboBox.SelectionChanged and ExampleClass.PropertyChanged events installed in my Window where I do the binding manually.

Is there a better or some kind of canonical way? Would you usually use Converters and how would you populate the ComboBox with the right values? I don't even want to get started with i18n right now.

Edit

So one question was answered: How do I populate the ComboBox with the right values.

Retrieve Enum values as a list of strings via an ObjectDataProvider from the static Enum.GetValues method:

<Window.Resources>
    <ObjectDataProvider MethodName="GetValues"
        ObjectType="{x:Type sys:Enum}"
        x:Key="ExampleEnumValues">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="ExampleEnum" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

This I can use as an ItemsSource for my ComboBox:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>

标签: .net wpf
13条回答
栀子花@的思念
2楼-- · 2019-01-01 05:04

You can create a custom markup extension.

Example of usage:

enum Status
{
    [Description("Available.")]
    Available,
    [Description("Not here right now.")]
    Away,
    [Description("I don't have time right now.")]
    Busy
}

At the top of your XAML:

    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

and then...

<ComboBox 
    ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" 
    DisplayMemberPath="Description" 
    SelectedValue="{Binding CurrentStatus}"  
    SelectedValuePath="Value"  /> 

And the implementation...

public class EnumerationExtension : MarkupExtension
  {
    private Type _enumType;


    public EnumerationExtension(Type enumType)
    {
      if (enumType == null)
        throw new ArgumentNullException("enumType");

      EnumType = enumType;
    }

    public Type EnumType
    {
      get { return _enumType; }
      private set
      {
        if (_enumType == value)
          return;

        var enumType = Nullable.GetUnderlyingType(value) ?? value;

        if (enumType.IsEnum == false)
          throw new ArgumentException("Type must be an Enum.");

        _enumType = value;
      }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      var enumValues = Enum.GetValues(EnumType);

      return (
        from object enumValue in enumValues
        select new EnumerationMember{
          Value = enumValue,
          Description = 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();
    }

    public class EnumerationMember
    {
      public string Description { get; set; }
      public object Value { get; set; }
    }
  }
查看更多
爱死公子算了
3楼-- · 2019-01-01 05:05

you can consider something like that:

  1. define a style for textblock, or any other control you want to use to display your enum:

        <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}">
            <Setter Property="Text" Value="&lt;NULL&gt;"/>
            <Style.Triggers>
                <Trigger Property="Tag">
                    <Trigger.Value>
                        <proj:YourEnum>Value1<proj:YourEnum>
                    </Trigger.Value>
                    <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/>
                </Trigger>
                <!-- add more triggers here to reflect your enum -->
            </Style.Triggers>
        </Style>
    
  2. define your style for ComboBoxItem

        <Style TargetType="{x:Type ComboBoxItem}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
  3. add a combobox and load it with your enum values:

            <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
                <ComboBox.Items>
                    <ComboBoxItem>
                        <proj:YourEnum>Value1</proj:YourEnum>
                    </ComboBoxItem>
                </ComboBox.Items>
            </ComboBox>
    

if your enum is large, you can of course do the same in code, sparing a lot of typing. i like that approach, since it makes localization easy - you define all the templates once, and then, you only update your string resource files.

查看更多
素衣白纱
4楼-- · 2019-01-01 05:08

In the viewmodel you can have:

    public MyEnumType SelectedMyEnumType 
    {
        get { return _selectedMyEnumType; }
        set { 
                _selectedMyEnumType = value;
                OnPropertyChanged("SelectedMyEnumType");
            }
    }

    public IEnumerable<MyEnumType> MyEnumTypeValues
    {
        get
        {
            return Enum.GetValues(typeof(MyEnumType))
                .Cast<MyEnumType>();
        }
    }

In XAML the ItemSource binds to MyEnumTypeValues and SelectedItem binds to SelectedMyEnumType.

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>
查看更多
笑指拈花
5楼-- · 2019-01-01 05:10

I prefer not to use the name of enum in UI. I prefer use different value for user (DisplayMemberPath) and different for value (enum in this case) (SelectedValuePath). Those two values can be packed to KeyValuePair and stored in dictionary.

XAML

<ComboBox Name="fooBarComboBox" 
          ItemsSource="{Binding Path=ExampleEnumsWithCaptions}" 
          DisplayMemberPath="Value" 
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" > 

C#

public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
    new Dictionary<ExampleEnum, string>()
    {
        {ExampleEnum.FooBar, "Foo Bar"},
        {ExampleEnum.BarFoo, "Reversed Foo Bar"},
        //{ExampleEnum.None, "Hidden in UI"},
    };


private ExampleEnum example;
public ExampleEnum ExampleProperty
{
    get { return example; }
    set { /* set and notify */; }
}

EDIT: Compatible with the MVVM pattern.

查看更多
回忆,回不去的记忆
6楼-- · 2019-01-01 05:11

I don't know if it is possible in XAML-only but try the following:

Give your ComboBox a name so you can access it in the codebehind: "typesComboBox1"

Now try the following

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));
查看更多
一个人的天荒地老
7楼-- · 2019-01-01 05:14

Based on the accepted but now deleted answer provided by ageektrapped I created a slimmed down version without some of the more advanced features. All the code is included here to allow you to copy-paste it and not get blocked by link-rot.

I use the System.ComponentModel.DescriptionAttribute which really is intended for design time descriptions. If you dislike using this attribute you may create your own but I think using this attribute really gets the job done. If you don't use the attribute the name will default to the name of the enum value in code.

public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

Here is the class used as the items source:

public class EnumItemsSource : Collection<String>, IValueConverter {

  Type type;

  IDictionary<Object, Object> valueToNameMap;

  IDictionary<Object, Object> nameToValueMap;

  public Type Type {
    get { return this.type; }
    set {
      if (!value.IsEnum)
        throw new ArgumentException("Type is not an enum.", "value");
      this.type = value;
      Initialize();
    }
  }

  public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.valueToNameMap[value];
  }

  public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.nameToValueMap[value];
  }

  void Initialize() {
    this.valueToNameMap = this.type
      .GetFields(BindingFlags.Static | BindingFlags.Public)
      .ToDictionary(fi => fi.GetValue(null), GetDescription);
    this.nameToValueMap = this.valueToNameMap
      .ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    Clear();
    foreach (String name in this.nameToValueMap.Keys)
      Add(name);
  }

  static Object GetDescription(FieldInfo fieldInfo) {
    var descriptionAttribute =
      (DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
    return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name;
  }

}

You can use it in XAML like this:

<Windows.Resources>
  <local:EnumItemsSource
    x:Key="ExampleEnumItemsSource"
    Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
  ItemsSource="{StaticResource ExampleEnumItemsSource}"
  SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/> 
查看更多
登录 后发表回答