将一个枚举属性数据绑定到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
你可以考虑这样的事情:
-
定义文本块的样式,或者你想用来显示你的枚举的任何其他控件:
<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,并加载你的枚举值:
<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
属性的简单类。 你可以像使用Item1
和Item2
那样方便地使用Tuple
,或者使用带有Key
和Value
的KeyValuePair
来代替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}" />