关于.net:将枚举属性数据绑定到WPF中的ComboBox

Databinding an enum property to a ComboBox in WPF

以下面的代码为例:

1
2
3
4
5
6
7
8
9
public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

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

我希望a将属性exampleproperty数据绑定到一个组合框,以便它显示选项"foobar"和"barfoo",并在模式twoway下工作。我希望我的组合框定义看起来像这样:

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

当前,我的窗口中安装了ComboBox.SelectionChanged和ExampleClass.PropertyChanged事件的处理程序,我在其中手动进行绑定。

有没有更好或某种规范的方法?您通常会使用转换器吗?如何用正确的值填充组合框?我现在甚至不想开始使用i18n。

编辑

因此,我们回答了一个问题:如何用正确的值填充组合框。

通过ObjectDataProvider从static enum.getValues方法检索作为字符串列表的枚举值:

1
2
3
4
5
6
7
8
9
<Window.Resources>
    <ObjectDataProvider MethodName="GetValues"
        ObjectType="{x:Type sys:Enum}"
        x:Key="ExampleEnumValues">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="ExampleEnum" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

我可以将其用作组合框的项源:

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

可以创建自定义标记扩展。

使用示例:

1
2
3
4
5
6
7
8
9
enum Status
{
    [Description("Available.")]
    Available,
    [Description("Not here right now.")]
    Away,
    [Description("I don't have time right now.")]
    Busy
}

在XAML的顶部:

1
    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

然后。。。

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

以及实施…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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; }
    }
  }

在ViewModel中,可以有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public MyEnumType SelectedMyEnumType
    {
        get { return _selectedMyEnumType; }
        set {
                _selectedMyEnumType = value;
                OnPropertyChanged("SelectedMyEnumType");
            }
    }

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

在XAML中,itemsource绑定到myEnumTypeValues,selectedItem绑定到selectedMyEnumType。

1
<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>


我不喜欢在UI中使用枚举的名称。我更喜欢对用户使用不同的值(DisplayMemberPath)和对值使用不同的值(本例中为枚举)(SelectedValuePath)。这两个值可以打包到KeyValuePair并存储在字典中。

XAML

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

C.*

1
2
3
4
5
public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
    new Dictionary<ExampleEnum, string>()
    {
        {ExampleEnum.FooBar,"Foo Bar
<div class="suo-content">[collapse title=""]<ul><li>我认为你的答案被低估了,考虑到ComboBox本身的期望,这似乎是最好的选择。也许您可以使用<wyn>Enum.GetValues</wyn>将字典生成器放在getter中,但这并不能解决要显示的部分名称。最后,特别是如果实现了i18n,那么如果枚举发生变化,您就必须手动更改内容。但如果有的话,枚举不应该经常改变,是吗?+ 1</li><li>请允许我按照您在评论中所写的方式来修改:<wyn>private static readonly Dictionary<ExampleEnum, string> EnumMapping = new Dictionary<ExampleEnum, string>() {     {ExampleEnum.FooBar,"Foo Bar"},     {ExampleEnum.BarFoo,"Reversed Foo Bar"},    &#47;&#47;{ExampleEnum.None,"Hidden in UI"}, };  public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions {     get     {         return EnumMapping;     } }</wyn>。</li><li>这个答案太棒了,它允许本地化枚举描述…谢谢你!</li><li>这个解决方案非常好,因为它处理枚举和本地化的代码比其他解决方案少!</li><li>字典的问题在于,这些键是按哈希值排序的,因此对此几乎没有控制权。虽然有点冗长,但我使用了list<keyvaluepair<enum,string>>。好主意。</li><li>@copernick@pragmatiek新修复:<wyn>public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } = new Dictionary<ExampleEnum, string>()         {             {ExampleEnum.FooBar,"Foo Bar"},             {ExampleEnum.BarFoo,"Reversed Foo Bar"},             &#47;&#47;{ExampleEnum.None,"Hidden in UI"},         };</wyn>。</li><li>@Jinji更新至C 6。您的修复使代码更短。谢谢。</li></ul>[/collapse]</div><hr><P>我不知道它是否仅在XAML中可用,但请尝试以下操作:</P><P>为组合框指定一个名称,以便您可以在"typescomboox1"代码后面访问它。</P><P>现在试试下面的</P>[cc]typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));

基于AgeekTrapper提供的已接受但现已删除的答案,我创建了一个精简版,没有一些更高级的功能。这里包含的所有代码都允许您复制粘贴它,而不会被link-rot阻塞。

我使用的System.ComponentModel.DescriptionAttribute实际上是用于设计时的描述。如果不喜欢使用此属性,可以创建自己的属性,但我认为使用此属性确实可以完成工作。如果不使用该属性,则名称将默认为代码中枚举值的名称。

1
2
3
4
5
6
7
8
9
public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

以下是用作项源的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
  }

}

您可以在XAML中使用它,如下所示:

1
2
3
4
5
6
7
8
<Windows.Resources>
  <local:EnumItemsSource
    x:Key="ExampleEnumItemsSource"
    Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
  ItemsSource="{StaticResource ExampleEnumItemsSource}"
  SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/>

使用ObjectDataProvider:

1
2
3
4
5
6
<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

然后绑定到静态资源:

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

在此日志中查找此解决方案


你可以考虑这样的事情:

  • 为textBlock或任何其他要用于显示枚举的控件定义样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        <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>
  • 定义ComboBoxitem的样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
        <Style TargetType="{x:Type ComboBoxItem}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
  • 添加组合框并用枚举值加载它:

    1
    2
    3
    4
    5
    6
    7
            <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
                <ComboBox.Items>
                    <ComboBoxItem>
                        <proj:YourEnum>Value1</proj:YourEnum>
                    </ComboBoxItem>
                </ComboBox.Items>
            </ComboBox>
  • 如果枚举很大,那么当然可以在代码中执行相同的操作,而不必进行大量的键入。我喜欢这种方法,因为它使本地化变得容易——您只定义一次所有模板,然后只更新字符串资源文件。


    我最喜欢的方法是使用ValueConverter,这样itemssource和selectedValue都绑定到同一属性。这不需要其他属性来保持ViewModel的整洁。

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

    以及转换器的定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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;
      }
    }

    此转换器可用于任何枚举。ValueDescription只是一个具有Value属性和Description属性的简单类。您可以使用TupleItem1Item2一起使用,或者使用KeyValue一起使用KeyValuePair,而不是您选择的值和描述或任何其他类,只要它可以保存枚举值和该枚举值的字符串描述。


    Here is a generic solution using a helper method.
    This can also handle an enum of any underlying type (byte, sbyte, uint, long, etc.)

    Helper Method:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static IEnumerable<object> GetEnum<T>() {
        var type    = typeof(T);
        var names   = Enum.GetNames(type);
        var values  = Enum.GetValues(type);
        var pairs   =
            Enumerable.Range(0, names.Length)
            .Select(i => new {
                    Name    = names.GetValue(i)
                ,   Value   = values.GetValue(i) })
            .OrderBy(pair => pair.Name);
        return pairs;
    }//method

    视图模型:

    16

    组合框:

    1
    2
    3
    4
    5
    6
    <ComboBox
        SelectedValue       ="{Binding SearchType}"
        ItemsSource         ="{Binding EnumSearchTypes}"
        DisplayMemberPath   ="Name"
        SelectedValuePath   ="Value"
    />

    如果您使用的是基于@rudigrobler答案的MVVM,则可以执行以下操作:

    将以下属性添加到ViewModel类中

    1
    public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum));

    然后在XAML中执行以下操作:

    1
    <ComboBox ItemsSource="{Binding ExampleEnumValues}" ... />

    我已经创建了一个开源的codeplex项目来实现这一点。您可以从这里下载nuget包。

    1
    <enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" />


    这是基于Gregor S.的最高投票答案(目前有128票)的DevExpress特定答案。

    这意味着我们可以在整个应用程序中保持样式一致:

    enter image description here

    不幸的是,如果不做一些修改,最初的答案无法与devexpress的ComboBoxEdit一起使用。

    首先,ComboBoxEdit的XAML:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}"
        SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        DisplayMember="Description"
        MinWidth="144" Margin="5"
        HorizontalAlignment="Left"
        IsTextEditable="False"
        ValidateOnTextInput="False"
        AutoComplete="False"
        IncrementalFiltering="True"
        FilterCondition="Like"
        ImmediatePopup="True"/>

    不用说,您需要将xamlExtensions指向包含XAML扩展类(定义如下)的命名空间:

    1
    xmlns:xamlExtensions="clr-namespace:XamlExtensions"

    我们必须将myEnum指向包含枚举的名称空间:

    1
    xmlns:myEnum="clr-namespace:MyNamespace"

    然后,枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    namespace MyNamespace
    {
        public enum EnumFilter
        {
            [Description("Free as a bird")]
            Free = 0,

            [Description("I'm Somewhat Busy")]
            SomewhatBusy = 1,

            [Description("I'm Really Busy")]
            ReallyBusy = 2
        }
    }

    Xaml的问题在于我们不能使用SelectedItemValue,因为这样会导致一个错误,因为setter是不可访问的(您有点疏忽,DevExpress)。因此,我们必须修改我们的ViewModel,以直接从对象获得价值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private EnumFilter _filterSelected = EnumFilter.All;
    public object FilterSelected
    {
        get
        {
            return (EnumFilter)_filterSelected;
        }
        set
        {
            var x = (XamlExtensionEnumDropdown.EnumerationMember)value;
            if (x != null)
            {
                _filterSelected = (EnumFilter)x.Value;
            }
            OnPropertyChanged("FilterSelected");
        }
    }

    为了完整性起见,这里是原始答案的XAML扩展(稍微重命名):

    25

    免责声明:我与DevExpress没有任何关系。Telerik也是一个伟大的图书馆。


    试用使用

    1
    2
    <ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
        SelectedValue="{Binding Path=ExampleProperty}" />


    推荐阅读