Databinding an enum property to a ComboBox in WPF

后端 未结 13 1378
北海茫月
北海茫月 2020-11-22 15:46

As an example take the following code:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEn         


        
相关标签:
13条回答
  • 2020-11-22 16:26

    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.

    0 讨论(0)
  • 2020-11-22 16:28

    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.

    0 讨论(0)
  • 2020-11-22 16:29

    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>
    
    0 讨论(0)
  • 2020-11-22 16:31

    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}}"/> 
    
    0 讨论(0)
  • 2020-11-22 16:33

    My favorite way to do this is with a ValueConverter so that the ItemsSource and SelectedValue both bind to the same property. This requires no additional properties to keep your ViewModel nice and clean.

    <ComboBox ItemsSource="{Binding Path=ExampleProperty, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
              SelectedValuePath="Value"
              DisplayMemberPath="Description"
              SelectedValue="{Binding Path=ExampleProperty}" />
    

    And the definition of the Converter:

    public static class EnumHelper
    {
      public static string Description(this Enum e)
      {
        return (e.GetType()
                 .GetField(e.ToString())
                 .GetCustomAttributes(typeof(DescriptionAttribute), false)
                 .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString();
      }
    }
    
    [ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
    public class EnumToCollectionConverter : MarkupExtension, IValueConverter
    {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
        return Enum.GetValues(value.GetType())
                   .Cast<Enum>()
                   .Select(e => new ValueDescription() { Value = e, Description = e.Description()})
                   .ToList();
      }
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
        return null;
      }
      public override object ProvideValue(IServiceProvider serviceProvider)
      {
        return this;
      }
    }
    

    This converter will work with any enum. ValueDescription is just a simple class with a Value property and a Description property. You could just as easily use a Tuple with Item1 and Item2, or a KeyValuePair with Key and Value instead of Value and Description or any other class of your choice as long as it has can hold an enum value and string description of that enum value.

    0 讨论(0)
  • 2020-11-22 16:35

    Use ObjectDataProvider:

    <ObjectDataProvider x:Key="enumValues"
       MethodName="GetValues" ObjectType="{x:Type System:Enum}">
          <ObjectDataProvider.MethodParameters>
               <x:Type TypeName="local:ExampleEnum"/>
          </ObjectDataProvider.MethodParameters>
     </ObjectDataProvider>
    

    and then bind to static resource:

    ItemsSource="{Binding Source={StaticResource enumValues}}"
    

    Find this solution at this blog

    0 讨论(0)
提交回复
热议问题