将枚举属性数据绑定到 WPF 中的组合框
Posted
技术标签:
【中文标题】将枚举属性数据绑定到 WPF 中的组合框【英文标题】:Databinding an enum property to a ComboBox in WPF 【发布时间】:2010-09-08 16:39:57 【问题描述】:以如下代码为例:
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 将枚举值作为字符串列表检索:
<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"/>
【问题讨论】:
我对此进行了探索,并提供了一个您可以在位于 here 的 WPF 中使用(完成本地化)的解决方案。 【参考方案1】:您可以创建自定义标记扩展。
使用示例:
enum Status
[Description("Available.")]
Available,
[Description("Not here right now.")]
Away,
[Description("I don't have time right now.")]
Busy
在 XAML 的顶部:
xmlns:my="clr-namespace:namespace_to_enumeration_extension_class
然后……
<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;
【讨论】:
@Gregor S. my:Enumeration 是什么? @Crown 'my' 是您在 xaml 文件顶部声明的命名空间前缀:例如 xmlns:my="clr-namespace:namespace_to_enumeration_extension_class。枚举是 EnumerationExtension 的缩写,在 xaml 中您不需要必须写出整个扩展类名。 +1,但是 WPF 完成最简单的事情所需的代码量确实令人头晕 我不太喜欢它让您在视图中使用对模型的一部分(枚举类型)的引用的方式,在ItemsSource
参数中。为了保持视图和模型解耦,我需要在 ViewModel 中创建枚举的副本并编写 ViewModel 代码以在两者之间进行转换……这将使解决方案不再那么简单。或者有没有办法从 ViewModel 提供类型本身?
另一个限制是,如果您有多种语言,则不能这样做。【参考方案2】:
在视图模型中你可以拥有:
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>
【讨论】:
这在我的 Universal 应用程序中运行得非常好,而且很容易实现。谢谢! 这非常有效,并且需要的代码要少得多。【参考方案3】:我不喜欢在 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; =
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 */;
编辑:与 MVVM 模式兼容。
【讨论】:
我认为您的回答被低估了,考虑到 ComboBox 本身的期望,这似乎是最好的选择。或许您可以使用Enum.GetValues
在getter 中放置一个字典构建器,但这并不能解决要显示的名称部分。最后,特别是如果实现了 I18n,无论如何,如果枚举发生更改,您将不得不手动更改内容。但是枚举不应该经常改变,如果有的话,是吗? +1
这个答案很棒,它允许本地化枚举描述......谢谢!
这个解决方案非常好,因为它处理枚举和本地化的代码比其他解决方案少!
Dictionary 的问题在于键是按哈希值排序的,因此几乎无法控制。虽然有点冗长,但我使用 Listpublic Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions get; = new Dictionary<ExampleEnum, string>() ExampleEnum.FooBar, "Foo Bar", ExampleEnum.BarFoo, "Reversed Foo Bar", //ExampleEnum.None, "Hidden in UI", ;
【参考方案4】:
我不知道在 XAML-only 中是否可行,但请尝试以下方法:
为您的 ComboBox 命名,以便您可以在代码隐藏中访问它:“typesComboBox1”
现在试试下面的
typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));
【讨论】:
【参考方案5】:使用 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"
找到这个solution at this blog
【讨论】:
不错的答案。顺便说一句,它使您不必担心枚举到字符串问题的Converter
。
链接解决方案似乎已死(韩文还是日文?)。如果我将您的代码放到我的 XAML 资源中,它会说 WPF 项目中不支持 Enum。
你需要在Window标签xlmns定义中添加'xmlns:System="clr-namespace:System;assembly=mscorlib"'
简单优雅的解决方案,这个需要提升!【参考方案6】:
根据ageektrapped 提供的已接受但现已删除的答案,我创建了一个精简版,但没有一些更高级的功能。所有代码都包含在此处,以允许您复制粘贴它而不会被链接腐烂阻止。
我使用System.ComponentModel.DescriptionAttribute
,它真正用于设计时描述。如果您不喜欢使用此属性,您可以创建自己的属性,但我认为使用此属性确实可以完成工作。如果您不使用该属性,则名称将默认为代码中枚举值的名称。
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"/>
【讨论】:
【参考方案7】:我最喜欢的方法是使用ValueConverter
,以便 ItemsSource 和 SelectedValue 都绑定到同一个属性。这需要不需要额外的属性来保持您的 ViewModel 干净整洁。
<ComboBox ItemsSource="Binding Path=ExampleProperty, Converter=x:EnumToCollectionConverter, Mode=OneTime"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="Binding Path=ExampleProperty" />
以及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;
此转换器适用于任何枚举。 ValueDescription
只是一个具有Value
属性和Description
属性的简单类。您可以轻松地将Tuple
与Item1
和Item2
一起使用,或者将KeyValuePair
与Key
和Value
一起使用,而不是Value 和Description 或您选择的任何其他类,只要它可以保存枚举值和该枚举值的字符串描述。
【讨论】:
不错的答案!对于ValueDescription
类,如果不需要,可以省略Description
属性。一个只有 Value
属性的简单类也可以工作!
另外,如果要绑定到 RadioButton,则 Convert 方法必须返回字符串列表,即 .Select(e => e.ToString())
,而不是使用 ValueDescription
类。
也可以使用KeyValuePair
代替ValueDescription
,例如shown here【参考方案8】:
你可以考虑这样的事情:
定义文本块的样式,或者您想用来显示枚举的任何其他控件:
<Style x:Key="enumStyle" TargetType="x:Type TextBlock">
<Setter Property="Text" Value="<NULL>"/>
<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 的样式
<Style TargetType="x:Type ComboBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Tag="Binding" Style="StaticResource enumStyle"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
添加一个组合框并使用您的枚举值加载它:
<ComboBox SelectedValue="Binding Path=your property goes here" SelectedValuePath="Content">
<ComboBox.Items>
<ComboBoxItem>
<proj:YourEnum>Value1</proj:YourEnum>
</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
如果你的枚举很大,你当然可以在代码中做同样的事情,节省大量的输入。 我喜欢这种方法,因为它使本地化变得容易 - 你定义所有模板一次,然后,你只更新你的字符串资源文件。
【讨论】:
SelectedValuePath="Content" 在这里帮助了我。我将我的 ComboBoxItems 作为字符串值,并且一直无法将 ComboBoxItem 转换为我的枚举类型。谢谢【参考方案9】:这是一个使用辅助方法的通用解决方案。 这也可以处理任何底层类型的枚举(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
SelectedValue ="Binding SearchType"
ItemsSource ="Binding EnumSearchTypes"
DisplayMemberPath ="Name"
SelectedValuePath ="Value"
/>
【讨论】:
【参考方案10】:如果您使用的是 MVVM,根据@rudigrobler 的回答,您可以执行以下操作:
将以下属性添加到 ViewModel 类
public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum));
然后在 XAML 中执行以下操作:
<ComboBox ItemsSource="Binding ExampleEnumValues" ... />
【讨论】:
【参考方案11】:这是一个 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"/>
不用说,您需要将 xamlExtensions
指向包含 XAML 扩展类(定义如下)的命名空间:
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 也是一个很棒的图书馆。
【讨论】:
郑重声明,我不隶属于 DevExpress。 Telerik 也有非常好的库,他们的库甚至可能不需要这种技术。【参考方案12】:尝试使用
<ComboBox ItemsSource="Binding Source=StaticResource ExampleEnumValues"
SelectedValue="Binding Path=ExampleProperty" />
【讨论】:
这不起作用。组合框只会显示一个空文本,更改它不会做任何事情。我想在这里加入转换器将是最好的解决方案。【参考方案13】:我创建了一个开源 CodePlex 项目来执行此操作。您可以从here 下载 NuGet 包。
<enumComboBox:EnumComboBox EnumType="x:Type demoApplication:Status" SelectedValue="Binding Status" />
【讨论】:
【参考方案14】:代码
public enum RULE
[Description( "Любые, без ограничений" )]
any,
[Description( "Любые если будет три в ряд" )]
anyThree,
[Description( "Соседние, без ограничений" )]
nearAny,
[Description( "Соседние если будет три в ряд" )]
nearThree
class ExtendRULE
public static object Values
get
List<object> list = new List<object>();
foreach( RULE rule in Enum.GetValues( typeof( RULE ) ) )
string desc = rule.GetType().GetMember( rule.ToString() )[0].GetCustomAttribute<DescriptionAttribute>().Description;
list.Add( new value = rule, desc = desc );
return list;
XAML
<StackPanel>
<ListBox ItemsSource= "Binding Source=x:Static model:ExtendRULE.Values" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="Binding SelectedRule"/>
<ComboBox ItemsSource="Binding Source=x:Static model:ExtendRULE.Values" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="Binding SelectedRule"/>
</StackPanel>
【讨论】:
【参考方案15】:看到某些过于复杂的解决方案如何成为最微不足道的问题的“标准(反)模式”令人痛苦:实现MarkupExtension
的开销和复杂性,尤其是用属性装饰枚举值应该避免。只需实现一个数据模型。
通常,向用户显示枚举值名称是一个坏主意。枚举并不意味着在 UI 中显示。它们是在编程上下文中使用的常量。值名称不用于显示。它们是针对工程师的,因此这些名称通常使用特殊的语义和词汇,就像科学词汇并不意味着公众可以理解一样。不要犹豫,为显示的值创建一个专用源。
当涉及到本地化时,问题变得更加明显。 这就是为什么所有发布的答案都只是过度设计的原因。他们使一个非常简单的问题看起来像一个关键问题。 事实上,最简单的解决方案是最好的。原始问题的主题绝对不是个例外。 我强烈建议不要提供任何答案。尽管它们可能有效,但它们给一个琐碎的问题增加了不必要的复杂性。
请注意,您始终可以通过调用静态 Enum.GetValues
或 Enum.GetNames
将枚举转换为其值或值名称的列表,它们都返回您可以直接分配给 ComboBox.ItemsSource
的 IEnumerable
属性,例如,通过数据绑定。
IEnumerable<ExampleEnum> values = Enum.GetValues<ExampleEnum>();
IEnumerable<string> names = Enum.GetNames<ExampleEnum>();
通常,在定义枚举时,您不会考虑 UI。
枚举值名称不是根据 UI 设计规则选择的。
通常,UI 标签和文本通常是由没有开发人员或程序员背景的人创建的。他们通常会提供本地化应用程序所需的所有翻译。
不将 UI 与应用程序混合使用有很多很好的理由。
你永远不会在设计一个类并为它的属性命名时考虑到 UI(例如,DataGrid
列)。您可能希望列标题包含空格等。
异常消息针对开发人员而不是用户的原因相同。您绝对不想用属性装饰每个属性、每个异常、枚举或任何数据类型或成员,以便在特定 UI 上下文中提供对用户有意义的显示名称。
您不希望 UI 设计渗入您的代码库并污染您的类。
应用程序及其用户界面 - 这是两个不同的问题。
添加这个抽象或虚拟的额外分离层允许例如添加不应显示的枚举值。或者更一般地说,修改代码而不必破坏或修改 UI。
您应该使用 simple IValueConverter
或提供这些显示值作为绑定源的专用类,而不是使用属性和实现加载额外逻辑来提取它们的值.
坚持最常见的模式并为ComboBox
项目实现数据模型,其中类具有枚举类型的属性作为成员,这有助于您识别ComboBox.SelectedItem
(以防您需要枚举值):
ExampleEnum.cs
// Define enumeration without minding any UI elements and context
public enum ExampleEnum
FooBar = 0,
BarFoo
ExampleClass.cs
// Define readable enum display values in the UI context.
// Display names can come from a localizable resource.
public class BindingSource : INotifyPropertyChanged
public BindingSource()
ItemModels = new List<ItemModel>
new ItemModel Label = "Foo Bar Display", Value = ExampleEnum.FooBar ,
new ItemModel Label = "Bar Foo Display", Value = ExampleEnum.BarFoo
public List<ItemModel> ItemModels get;
private ItemModel selectedItemModel;
public ItemModel SelectedItemModel get => selectedItemModel; => set and notify;
ItemModel.cs
public class ItemModel
public string Label get; set;
public ExampleEnum Value get; set;
MainWindow.xaml
<Window>
<Window.DataContext>
<BindingSource />
</Window.DataContext>
<ComboBox ItemsSource="Binding ItemModels"
DisplayMemberName="DisplayValue"
SelectedItem="Binding SelectedItemModel" />
</Window>
【讨论】:
以上是关于将枚举属性数据绑定到 WPF 中的组合框的主要内容,如果未能解决你的问题,请参考以下文章
WPF 数据绑定:如何使用 XAML 将枚举数据绑定到组合框? [复制]
DataGrid 数据绑定/更新中的 WPF 组合框不起作用