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中使用枚举的名称。我更喜欢对用户使用不同的值(
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"}, //{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"}, //{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阻塞。
我使用的
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="<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的样式
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> |
如果枚举很大,那么当然可以在代码中执行相同的操作,而不必进行大量的键入。我喜欢这种方法,因为它使本地化变得容易——您只定义一次所有模板,然后只更新字符串资源文件。
我最喜欢的方法是使用
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; } } |
此转换器可用于任何枚举。
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}" /> |
这是基于
这意味着我们可以在整个应用程序中保持样式一致:
不幸的是,如果不做一些修改,最初的答案无法与devexpress的
首先,
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"/> |
不用说,您需要将
1 | xmlns:xamlExtensions="clr-namespace:XamlExtensions" |
我们必须将
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的问题在于我们不能使用
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}" /> |