如何将枚举绑定到WPF中的combobox控件?

我正在试图find一个简单的例子,其中显示的是枚举枚举。 我见过的所有例子都试图添加漂亮的显示string,但我不想那么复杂。

基本上我有一个类,它拥有我绑定的所有属性,首先将DataContext设置为这个类,然后在xaml文件中指定这样的绑定:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/> 

但是这不会显示ComboBox框中的枚举值作为项目。

您可以通过在Window Loaded事件处理程序中放置以下代码从代码执行此操作,例如:

 yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>(); 

如果需要在XAML中绑定它,则需要使用ObjectDataProvider创build可用作绑定源的对象:

 <Window x:Class="YourNamespace.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"> <Window.Resources> <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}"> <ObjectDataProvider.MethodParameters> <x:Type TypeName="StyleAlias:EffectStyle"/> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </Window.Resources> <Grid> <ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}" SelectedItem="{Binding Path=CurrentEffectStyle}" /> </Grid> </Window> 

提请注意下一个代码:

 xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:StyleAlias="clr-namespace:Motion.VideoEffects" 

指导如何映射名称空间和程序集,您可以在MSDN上阅读。

我喜欢所有绑定的对象在我的ViewModel定义,所以我尽量避免在xaml中使用<ObjectDataProvider>

当我想将Enum绑定到ComboBox ,我想要显示的文本永远不会与Enum的值相匹配,所以我使用[Description()]属性给它实际上我想在ComboBox看到的文本。 如果我在游戏中有一个angular色类的枚举,那么看起来像这样:

 public enum PlayerClass { // add an optional blank value for default/no selection [Description("")] NOT_SET = 0, [Description("Shadow Knight")] SHADOW_KNIGHT, ... } 

首先,我用几个方法创build了帮助类来处理枚举。 一个方法获取特定值的描述,另一个方法获取一个types的所有值及其描述。

 public static class EnumHelper { public static string Description(this Enum eValue) { var nAttributes = eValue.GetType().GetField(eValue.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); if (nAttributes.Any()) return (nAttributes.First() as DescriptionAttribute).Description; // If no description is found, the least we can do is replace underscores with spaces // You can add your own custom default formatting logic here TextInfo oTI = CultureInfo.CurrentCulture.TextInfo; return oTI.ToTitleCase(oTI.ToLower(eValue.ToString().Replace("_", " "))); } public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t) { if (!t.IsEnum) throw new ArgumentException("t must be an enum type"); return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList(); } } 

接下来,我们创build一个ValueConverter 。 inheritanceMarkupExtension使得在XAML中使用起来更容易,所以我们不必将其声明为资源。

 [ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))] public class EnumToCollectionConverter : MarkupExtension, IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return EnumHelper.GetAllValuesAndDescriptions(value.GetType()); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } public override object ProvideValue(IServiceProvider serviceProvider) { return this; } } 

我的ViewModel只需要一个属性,我的View可以绑定到ComboBox的SelectedValueItemsSource

 private PlayerClass p_eClass; public PlayerClass SelectedClass { get { return p_eClass; } set { if (p_eClass != value) { p_eClass = value; OnPropertyChanged(nameof(SelectedClass)); } } } 

最后绑定ComboBox视图(在ItemsSource绑定中使用ValueConverter )…

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

要实现这个解决scheme,你只需要复制我的EnumHelper类和EnumToCollectionConverter类。 他们将与任何枚举一起工作。 另外,我没有在这里包括它,但是ValueDescription类只是一个带有2个公共对象属性的简单类,一个叫Value ,一个叫Description 。 您可以自己创build,也可以更改代码以使用Tuple<object, object>KeyValuePair<object, object>

我用另一个解决scheme使用MarkupExtension。

  1. 我做了提供项目的课程来源:

     public class EnumToItemsSource : MarkupExtension { private readonly Type _type; public EnumToItemsSource(Type type) { _type = type; } public override object ProvideValue(IServiceProvider serviceProvider) { return Enum.GetValues(_type) .Cast<object>() .Select(e => new { Value = (int)e, DisplayName = e.ToString() }); } } 
  2. 这几乎全部…现在在XAML中使用它:

      <ComboBox DisplayMemberPath="DisplayName" ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}" SelectedValue="{Binding Path=WhereEverYouWant}" SelectedValuePath="Value" /> 
  3. 将“枚举:国家”更改为您的枚举

使用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}}" 

您需要在枚举中创build一个值的数组,可以通过调用System.Enum.GetValues()来创build值,并将其传递给您想要项目的枚举Type

如果您为ItemsSource属性指定此值,则应该使用所有枚举值填充它。 您可能想要将SelectedItem绑定到EffectStyle (假设它是一个枚举属性相同,并包含当前值)。

Nick的回答确实对我有帮助,但我意识到可以稍微调整一下,以避免额外的类,ValueDescription。 我记得在框架中已经存在一个KeyValuePair类,所以可以用它来代替。

代码只是稍微改变:

 public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable { if (!typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an Enumeration type"); } return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>() select new KeyValuePair<string, string>(e.ToString(), e.Description()); } public IEnumerable<KeyValuePair<string, string>> PlayerClassList { get { return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>(); } } 

最后是XAML:

 <ComboBox ItemSource="{Binding Path=PlayerClassList}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Path=SelectedClass}" /> 

我希望这对别人有帮助。

所有上面的post都错过了一个简单的窍门。 可以从SelectedValue的绑定中找出如何填充ItemsSource的AUTOMAGICALLY,以便您的XAML标记正好。

 <Controls:EnumComboBox SelectedValue="{Binding Fool}"/> 

例如在我的ViewModel中有

 public enum FoolEnum { AAA, BBB, CCC, DDD }; FoolEnum _Fool; public FoolEnum Fool { get { return _Fool; } set { ValidateRaiseAndSetIfChanged(ref _Fool, value); } } 

ValidateRaiseAndSetIfChanged是我的INPC钩子。 你可能会有所不同。

EnumComboBox的实现如下,但首先我需要一个小帮手来获取我的枚举string和值

  public static List<Tuple<object, string, int>> EnumToList(Type t) { return Enum .GetValues(t) .Cast<object>() .Select(x=>Tuple.Create(x, x.ToString(), (int)x)) .ToList(); } 

和主类(注意我使用ReactiveUI通过WhenAny挂钩属性更改)

 using ReactiveUI; using ReactiveUI.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Windows; using System.Windows.Documents; namespace My.Controls { public class EnumComboBox : System.Windows.Controls.ComboBox { static EnumComboBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox))); } protected override void OnInitialized( EventArgs e ) { base.OnInitialized(e); this.WhenAnyValue(p => p.SelectedValue) .Where(p => p != null) .Select(o => o.GetType()) .Where(t => t.IsEnum) .DistinctUntilChanged() .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(FillItems); } private void FillItems(Type enumType) { List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>(); foreach (var idx in EnumUtils.EnumToList(enumType)) { values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2)); } this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList(); UpdateLayout(); this.ItemsSource = values; this.DisplayMemberPath = "Value"; this.SelectedValuePath = "Key"; } } } 

您还需要在Generic.XAML中正确设置样式,否则您的盒子将不会渲染任何东西,并且会将您的头发拉出。

 <Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}"> </Style> 

就是这样 这显然可以延长,以支持国际化,但会使post更长。

 public class EnumItemsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!value.GetType().IsEnum) return false; var enumName = value.GetType(); var obj = Enum.Parse(enumName, value.ToString()); return System.Convert.ToInt32(obj); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return Enum.ToObject(targetType, System.Convert.ToInt32(value)); } } 

如果直接绑定枚举对象模型属性,则应该使用这种Enum值转换器来扩展Rogers和Greg的答案。

如果你绑定到你的ViewModel的实际枚举属性,而不是一个枚举的int表示,事情变得棘手。 我发现有必要绑定到string表示,而不是所有上面的例子中预期的int值。

你可以通过将一个简单的文本框绑定到你想要绑定到ViewModel上的属性来判断是否属于这种情况。 如果显示文本,则绑定到string。 如果显示一个数字,则绑定到该值。 注意我已经使用了两次显示,这通常是一个错误,但它是唯一的方法。

 <ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}" DisplayMemberPath="Display" SelectedValuePath="Display" ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" /> 

格雷格

通用应用似乎有点不同, 它不具备全functionXAML的全部function。 对我有效的是:

  1. 我创build了枚举值的列表作为枚举(不转换为string或整数),并将combobox的ItemsSource绑定到
  2. 然后我可以将ComboBox ItemSelected绑定到我的公有属性,其types是所讨论的枚举

为了好玩,我创build了一个模板类来帮助解决这个问题,并将其发布到MSDN示例页面 。 额外的位让我可以覆盖枚举的名称,让我隐藏一些枚举。 我的代码看起来像尼克(上图),我希望我以前看到过。

运行样本;它包含了enum的多个双向绑定

我喜欢tom.maruska的答案 ,但是我需要支持我的模板在运行时可能遇到的任何枚举types。 为此,我必须使用绑定来指定标记扩展的types。 我能够从nicolay.anykienko 这个答案工作来提出一个非常灵活的标记扩展,无论如何我能想到的工作。 它是这样消耗的:

 <ComboBox SelectedValue="{Binding MyEnumProperty}" SelectedValuePath="Value" ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" DisplayMemberPath="DisplayName" /> 

上面引用的混搭标记扩展的来源:

 class EnumToObjectArray : MarkupExtension { public BindingBase SourceEnum { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; DependencyObject targetObject; DependencyProperty targetProperty; if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty) { targetObject = (DependencyObject)target.TargetObject; targetProperty = (DependencyProperty)target.TargetProperty; } else { return this; } BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum); var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType(); if (type.BaseType != typeof(System.Enum)) return this; return Enum.GetValues(type) .Cast<Enum>() .Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) }); } private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum) , typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); /// <summary> /// Extension method which returns the string specified in the Description attribute, if any. Oherwise, name is returned. /// </summary> /// <param name="value">The enum value.</param> /// <returns></returns> public static string Description(Enum value) { var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); if (attrs.Any()) return (attrs.First() as DescriptionAttribute).Description; //Fallback return value.ToString().Replace("_", " "); } } 

我添加了我的评论(在VB中,不幸的是,这个概念可以很容易地被复制到C#中),因为我只需要引用它,并且不喜欢任何答案,因为它们太复杂了。 这不应该是这个困难。

所以我想出了一个更简单的方法。 将枚举器绑定到一个字典。 将该字典绑定到Combobox。

我的combobox:

 <ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" SelectedValuePath="Key" DisplayMemberPath="Value" /> 

我的代码隐藏 希望这能帮助别人。

  Dim tDict As New Dictionary(Of Integer, String) Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType)) For Each x As Helper.Enumerators.AllowedType In types Dim z = x.ToString() Dim y = CInt(x) tDict.Add(y, z) Next cmbRole.ClearValue(ItemsControl.ItemsSourceProperty) cmbRole.ItemsSource = tDict 

使用ReactiveUI ,我创build了以下备用解决scheme。 这不是一个优雅的一体化解决scheme,但我认为至less它是可读的。

在我的情况下,将enum列表绑定到控件是一种罕见的情况,所以我不需要在整个代码基础上扩展解决scheme。 但是,通过将EffectStyleLookup.Item更改为Object可以使代码变得更加通用。 我用我的代码进行了testing,没有其他修改是必要的。 这意味着一个助手类可以应用于任何enum列表。 虽然这会降低其可读性 – ReactiveList<EnumLookupHelper>没有一个伟大的戒指。

使用以下辅助类:

 public class EffectStyleLookup { public EffectStyle Item { get; set; } public string Display { get; set; } } 

在ViewModel中,将枚举列表转换为属性:

 public ViewModel : ReactiveObject { private ReactiveList<EffectStyleLookup> _effectStyles; public ReactiveList<EffectStyleLookup> EffectStyles { get { return _effectStyles; } set { this.RaiseAndSetIfChanged(ref _effectStyles, value); } } // See below for more on this private EffectStyle _selectedEffectStyle; public EffectStyle SelectedEffectStyle { get { return _selectedEffectStyle; } set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); } } public ViewModel() { // Convert a list of enums into a ReactiveList var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle)) .Select( x => new EffectStyleLookup() { Item = x, Display = x.ToString() }); EffectStyles = new ReactiveList<EffectStyle>( list ); } } 

ComboBox ,利用SelectedValuePath属性绑定到原始的enum值:

 <ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" /> 

在视图中,这允许我们将原始enum绑定到ViewModel中的SelectedEffectStyle ,但在ComboBox显示ToString()值:

 this.WhenActivated( d => { d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) ); d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) ); });