将一个枚举属性数据绑定到WPF中的combobox

以下面的代码为例:

public enum ExampleEnum { FooBar, BarFoo } public class ExampleClass : INotifyPropertyChanged { private ExampleEnum example; public ExampleEnum ExampleProperty { get { return example; } { /* set and notify */; } } } 

我想要一个数据绑定属性ExampleProperty到一个combobox,以便它显示选项“FooBar”和“BarFoo”,并在模式TwoWay中工作。 最好我想我的ComboBox定义看起来像这样:

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

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

有没有更好的或某种规范的方式? 你会经常使用转换器,你将如何填充正确的值combobox? 我甚至不想立即开始使用i18n。

编辑

所以有一个问题的答案是:我如何使用正确的值填充combobox。

通过来自静态Enum.GetValues方法的ObjectDataProvider检索Enum值作为string列表:

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

这可以用作我的ComboBox的ItemsSource:

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

您可以创build一个自定义标记扩展。

使用示例:

 enum Status { [Description("Available.")] Available, [Description("Not here right now.")] Away, [Description("I don't have time right now.")] Busy } 
 <ComboBox ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" DisplayMemberPath="Description" SelectedValue="{Binding CurrentStatus}" SelectedValuePath="Value" /> 

而实施…

 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; } } } 

在视图模型中,您可以拥有:

  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。

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

我不喜欢在UI中使用枚举的名称。 我更喜欢为用户( DisplayMemberPath )使用不同的值,而不同的值(在这种情况下为枚举)( SelectedValuePath )。 这两个值可以打包到KeyValuePair并存储在字典中。

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 { return new Dictionary<ExampleEnum, string>() // Fix. Each time new dict.? { {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 */; } } 

编辑:与MVVM模式兼容。

我不知道是否可以在XAML中使用,但请尝试以下操作:

给你的ComboBox一个名字,这样你就可以在代码隐藏中访问它:“typesComboBox1”

现在尝试以下

 typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum)); 

基于ageektrapped提供的被接受但现在被删除的答案,我创build了一个瘦身版本,没有一些更高级的function。 所有的代码都包含在这里,允许你复制粘贴它,而不是被链接腐烂阻塞。

我使用System.ComponentModel.DescriptionAttribute ,这是真正用于devise时间描述。 如果你不喜欢使用这个属性,你可以创build自己的属性,但是我认为使用这个属性真的可以完成任务。 如果您不使用该属性,则该名称将默认为代码中的枚举值的名称。

 public enum ExampleEnum { [Description("Foo Bar")] FooBar, [Description("Bar Foo")] BarFoo } 

以下是用作项目源的类:

 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中使用它:

 <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:

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

然后绑定到静态资源:

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

在这个博客find这个解决scheme

你可以考虑这样的事情:

  1. 定义文本块的样式,或者你想用来显示你的枚举的任何其他控件:

      <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. 定义ComboBoxItem的样式

      <Style TargetType="{x:Type ComboBoxItem}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/> </DataTemplate> </Setter.Value> </Setter> </Style> 
  3. 添加一个combobox,并加载你的枚举值:

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

如果你的枚举很大,你当然可以在代码中做同样的事情,不要打字。 我喜欢这种方法,因为它使得本地化变得简单 – 您只需定义一次所有的模板,然后只更新string资源文件。

这是一个使用辅助方法的通用解决scheme。 这也可以处理任何基础types的枚举(byte,sbyte,uint,long等)

帮手方法:

 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 

查看模型:

 public IEnumerable<object> EnumSearchTypes { get { return GetEnum<SearchTypes>(); } }//property 

combobox:

 <ComboBox SelectedValue ="{Binding SearchType}" ItemsSource ="{Binding EnumSearchTypes}" DisplayMemberPath ="Name" SelectedValuePath ="Value" /> 

这是一个DevExpress具体答案,基于Gregor S.最高票数的答案(目前它有128票)。

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

在这里输入图像描述

不幸的是,最初的答案不适用于DevExpress的ComboBoxEdit而没有做任何修改。

首先, ComboBoxEdit的XAML:

 <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"/> 

无需多言,您将需要在包含XAML扩展类(下面定义)的名称空间处指向xamlExtensions

 xmlns:xamlExtensions="clr-namespace:XamlExtensions" 

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

 xmlns:myEnum="clr-namespace:MyNamespace" 

然后,枚举:

 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直接从对象中获取值:

 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扩展(稍加改名):

 namespace XamlExtensions { /// <summary> /// Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the /// dropdown box by using the [Description] attribute on the enum values. /// </summary> public class XamlExtensionEnumDropdown : MarkupExtension { private Type _enumType; public XamlExtensionEnumDropdown(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(); } #region Nested type: EnumerationMember public class EnumerationMember { public string Description { get; set; } public object Value { get; set; } } #endregion } } 

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

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

将以下属性添加到ViewModel

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

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

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

我最喜欢的做法是使用ValueConverter以便ItemsSource和SelectedValue都绑定到相同的属性。 这不需要额外的属性来保持你的ViewModel漂亮干净。

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

而转换器的定义:

 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属性的简单类。 你可以像使用Item1Item2那样方便地使用Tuple ,或者使用带有KeyValueKeyValuePair来代替Value和Description或者其他任何你select的类,只要它有可以持有该枚举值的枚举值和string描述。

尝试使用

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

我已经创build了一个开源的CodePlex项目。 你可以从这里下载NuGet软件包。

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