.net 将枚举属性数据绑定到 WPF 中的 ComboBox
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/58743/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Databinding an enum property to a ComboBox in WPF
提问by Maximilian
As an example take the following code:
以下面的代码为例:
public enum ExampleEnum { FooBar, BarFoo }
public class ExampleClass : INotifyPropertyChanged
{
private ExampleEnum example;
public ExampleEnum ExampleProperty
{ get { return example; } { /* set and notify */; } }
}
I want a to databind the property ExampleProperty to a ComboBox, so that it shows the options "FooBar" and "BarFoo" and works in mode TwoWay. Optimally I want my ComboBox definition to look something like this:
我想要将属性 ExampleProperty 数据绑定到 ComboBox,以便它显示选项“FooBar”和“BarFoo”并在 TwoWay 模式下工作。最好我希望我的 ComboBox 定义看起来像这样:
<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />
Currently I have handlers for the ComboBox.SelectionChanged and ExampleClass.PropertyChanged events installed in my Window where I do the binding manually.
目前,我在我的窗口中安装了 ComboBox.SelectionChanged 和 ExampleClass.PropertyChanged 事件的处理程序,我在其中手动执行绑定。
Is there a better or some kind of canonical way? Would you usually use Converters and how would you populate the ComboBox with the right values? I don't even want to get started with i18n right now.
有没有更好的或某种规范的方式?您通常会使用转换器吗?如何使用正确的值填充 ComboBox?我现在什至不想开始使用 i18n。
Edit
编辑
So one question was answered: How do I populate the ComboBox with the right values.
所以回答了一个问题:如何使用正确的值填充 ComboBox。
Retrieve Enum values as a list of strings via an ObjectDataProvider from the static Enum.GetValues method:
通过静态 Enum.GetValues 方法中的 ObjectDataProvider 以字符串列表的形式检索 Enum 值:
<Window.Resources>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="ExampleEnumValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="ExampleEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
This I can use as an ItemsSource for my ComboBox:
我可以将其用作 ComboBox 的 ItemsSource:
<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>
回答by Gregor Slavec
You can create a custom markup extension.
您可以创建自定义标记扩展。
Example of usage:
用法示例:
enum Status
{
[Description("Available.")]
Available,
[Description("Not here right now.")]
Away,
[Description("I don't have time right now.")]
Busy
}
At the top of your XAML:
在 XAML 的顶部:
xmlns:my="clr-namespace:namespace_to_enumeration_extension_class
and then...
进而...
<ComboBox
ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}"
DisplayMemberPath="Description"
SelectedValue="{Binding CurrentStatus}"
SelectedValuePath="Value" />
And the implementation...
和实施...
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; }
}
}
回答by user659130
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 ItemSourcebinds to MyEnumTypeValuesand SelectedItembinds to SelectedMyEnumType.
在 XAML 中,ItemSource绑定到MyEnumTypeValues并SelectedItem绑定到 SelectedMyEnumType.
<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>
回答by CoperNick
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 KeyValuePairand stored in dictionary.
我不想在 UI 中使用 enum 的名称。我更喜欢对用户 ( DisplayMemberPath)使用不同的值,对值使用不同的值(在这种情况下是枚举) ( SelectedValuePath)。这两个值可以打包KeyValuePair并存储在字典中。
XAML
XAML
<ComboBox Name="fooBarComboBox"
ItemsSource="{Binding Path=ExampleEnumsWithCaptions}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" >
C#
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.
编辑:与 MVVM 模式兼容。
回答by rudigrobler
I don't know if it is possible in XAML-only but try the following:
我不知道在仅 XAML 中是否可行,但请尝试以下操作:
Give your ComboBox a name so you can access it in the codebehind: "typesComboBox1"
为您的 ComboBox 命名,以便您可以在代码隐藏中访问它:“typesComboBox1”
Now try the following
现在尝试以下
typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));
回答by Martin Liversage
Based on the accepted but now deleted answer provided by ageektrappedI 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.
根据ageektrapped提供的已接受但现已删除的答案,我创建了一个精简版,没有一些更高级的功能。所有代码都包含在此处,以允许您复制粘贴它而不会被链接腐烂阻止。
I use the System.ComponentModel.DescriptionAttributewhich 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.
我使用System.ComponentModel.DescriptionAttribute真正用于设计时描述的 。如果您不喜欢使用此属性,您可以创建自己的属性,但我认为使用此属性确实可以完成工作。如果您不使用该属性,则名称将默认为代码中枚举值的名称。
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:
您可以像这样在 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}}"/>
回答by druss
Use ObjectDataProvider:
使用 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
回答by Nick
My favorite way to do this is with a ValueConverterso that the ItemsSource and SelectedValue both bind to the same property. This requires no additional propertiesto keep your ViewModel nice and clean.
我最喜欢的方法是使用 aValueConverter以便 ItemsSource 和 SelectedValue 都绑定到相同的属性。这不需要额外的属性来保持你的 ViewModel 漂亮和干净。
<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. ValueDescriptionis just a simple class with a Valueproperty and a Descriptionproperty. You could just as easily use a Tuplewith Item1and Item2, or a KeyValuePairwith Keyand Valueinstead 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.
此转换器适用于任何枚举。ValueDescription只是一个带有Value属性和Description属性的简单类。您可以轻松地使用Tuplewith Item1and Item2,或KeyValuePairwith KeyandValue代替 Value 和 Description 或您选择的任何其他类,只要它可以保存枚举值和该枚举值的字符串描述。
回答by Greg
you can consider something like that:
你可以考虑这样的事情:
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="<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>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>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>
为 textblock 或任何其他要用于显示枚举的控件定义样式:
<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>
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.
如果您的枚举很大,您当然可以在代码中执行相同的操作,从而节省大量输入。我喜欢这种方法,因为它使本地化变得容易——你定义所有模板一次,然后,你只更新你的字符串资源文件。
回答by Hyman
Here is a generic solution using a helper method. This can also handle an enum of any underlying type (byte, sbyte, uint, long, etc.)
这是使用辅助方法的通用解决方案。这也可以处理任何基础类型的枚举(byte、sbyte、uint、long 等)
Helper Method:
辅助方法:
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
View Model:
查看型号:
public IEnumerable<object> EnumSearchTypes {
get {
return GetEnum<SearchTypes>();
}
}//property
ComboBox:
组合框:
<ComboBox
SelectedValue ="{Binding SearchType}"
ItemsSource ="{Binding EnumSearchTypes}"
DisplayMemberPath ="Name"
SelectedValuePath ="Value"
/>
回答by Contango
This is a DevExpressspecific answer based on the top-voted answer by Gregor S.(currently it has 128 votes).
这是DevExpress基于最高投票答案的特定答案Gregor S.(目前有 128 票)。
This means we can keep the styling consistent across the entire application:
这意味着我们可以在整个应用程序中保持样式一致:


Unfortunately, the original answer doesn't work with a ComboBoxEditfrom DevExpress without some modifications.
不幸的是,ComboBoxEdit未经修改,原始答案不适用于DevExpress 中的a 。
First, the XAML for the ComboBoxEdit:
首先,XAML 用于ComboBoxEdit:
<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"/>
Needsless to say, you will need to point xamlExtensionsat the namespace that contains the XAML extension class (which is defined below):
不用说,您需要指向xamlExtensions包含 XAML 扩展类(定义如下)的命名空间:
xmlns:xamlExtensions="clr-namespace:XamlExtensions"
And we have to point myEnumat the namespace that contains the enum:
我们必须指向myEnum包含枚举的命名空间:
xmlns:myEnum="clr-namespace:MyNamespace"
Then, the enum:
然后,枚举:
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
}
}
The problem in with the XAML is that we can't use SelectedItemValue, as this throws an error as the setter is unaccessable (bit of an oversight on your part, DevExpress). So we have to modify our ViewModelto obtain the value directly from the object:
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");
}
}
For completeness, here is the XAML extension from the original answer (slightly renamed):
为了完整起见,这里是原始答案中的 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
}
}
Disclaimer: I have no affiliation with DevExpress. Telerik is also a great library.
免责声明:我与 DevExpress 没有任何关系。Telerik 也是一个很棒的图书馆。

