关于.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}" />


    推荐阅读

      linux编译源代码命令?

      linux编译源代码命令?,工具,代码,百度,最新,环境,项目,系统,电脑,密码,内核,l

      linux命令提交代码?

      linux命令提交代码?,工作,系统,地址,代码,命令,数据,信息,目录,标准,发行,求

      linux代码同步命令?

      linux代码同步命令?,时间,服务,系统,地址,代码,网络,通信,图片,风险,管理,lin

      linux命令错误代码?

      linux命令错误代码?,系统,密码,电脑,网络,手机,网址,软件,代码,设备,老板,Lin

      linux同步代码命令?

      linux同步代码命令?,时间,系统,通信,网络,标准,图片,服务,代码,线程,单位,Lin

      linux拉取代码命令?

      linux拉取代码命令?,代码,工作,地址,命令,数据,系统,单位,生产,软件,目录,lin

      linux命令查看包属性?

      linux命令查看包属性?,时间,系统,信息,状态,命令,文件,通讯录,管理,情况,标

      linux代码对齐命令?

      linux代码对齐命令?,系统,地址,标准,信息,对比,名称,代码,命令,文件,工作,lin

      linux命令运行代码?

      linux命令运行代码?,代码,单位,系统,环境,连续,保险,工具,命令,文件,音乐,Lin

      搭建linux命令行代码?

      搭建linux命令行代码?,系统,软件,工作,名字,服务,代码,地址,环境,管理,密码,l

      linux查看命令代码?

      linux查看命令代码?,系统,信息,代码,名称,命令,设备,数字,第一,软件,管理,在L

      linux删除代码命令行?

      linux删除代码命令行?,系统,代码,命令,文件,不了,环境,档案,名称,目录,文件

      linux命令行代码实现?

      linux命令行代码实现?,标准,代码,管理,网络,地址,工作,命令,网上,环境,名称,

      linux桌面命令代码?

      linux桌面命令代码?,电脑,系统,密码,环境,代码,基础,地址,服务,网上,通讯,lin

      c代码执行linux命令?

      c代码执行linux命令?,系统,工作,标准,情况,代码,环境,设备,命令,函数,指令,li

      linux内核属性命令?

      linux内核属性命令?,系统,地址,时间,信息,标准,管理,数据,工作,百分比,内核,

      linux进入代码行命令?

      linux进入代码行命令?,系统,代码,设备,终端,环境,信息,第一,命令,窗口,模式,

      linux命令行看代码?

      linux命令行看代码?,代码,基础,系统,命令,数字,工作,情况,进程,程序,终端,在L

      linux命令代码怎么看?

      linux命令代码怎么看?,时间,系统,代码,命令,状态,工具,情况,电脑,实时,基础,l