首页 文章

如何将枚举绑定到WPF中的组合框控件?

提问于
浏览
151

我试图找到一个简单的例子,其中枚举按原样显示 . 我见过的所有示例都尝试添加漂亮的显示字符串,但我不希望这种复杂性 .

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

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

但这并未将 ComboBox 中的枚举值显示为项目 .

16 回答

  • 1

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

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

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

    <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上阅读的命名空间和程序集 .

  • 4

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

    我的解决方案不使用View中定义的数据,也不使用代码隐藏 . 只有一个DataBinding,一个可重用的ValueConverter,一个获取任何Enum类型描述集合的方法,以及要绑定到ViewModel中的单个属性 .

    当我想将 Enum 绑定到 ComboBox 时,我想要显示的文本永远不会匹配 Enum 的值,所以我使用 [Description()] 属性为它提供我真正想要在_1486729中看到的文本 . 如果我在游戏中有一个字符类枚举,它看起来像这样:

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

    首先,我使用几种方法创建了辅助类来处理枚举 . 一种方法获取特定值的描述,另一种方法获取所有值及其对类型的描述 .

    public static class EnumHelper
    {
      public static string Description(this Enum value)
      {
        var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Any())
          return (attributes.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 ti = CultureInfo.CurrentCulture.TextInfo;
        return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
      }
    
      public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
      {
        if (!t.IsEnum)
          throw new ArgumentException($"{nameof(t)} must be an enum type");
    
        return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
      }
    }
    

    接下来,我们创建一个 ValueConverter . 继承自 MarkupExtension 使得在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 可以绑定到组合框的 SelectedValueItemsSource

    private PlayerClass playerClass;
    
    public PlayerClass SelectedClass
    {
      get { return playerClass; }
      set
      {
        if (playerClass != value)
        {
          playerClass = 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}" />
    

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

  • 2

    我使用了另一种使用MarkupExtension的解决方案 .

    • 我做了一个提供项目来源的课程:
    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() });
        }
    }
    
    • 这几乎都是......现在在XAML中使用它:
    <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
    
    • 将“enums:States”更改为您的枚举
  • 0

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

    尼克的回答确实帮助了我,但我意识到它可以稍微调整一下,以避免额外的类,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}" />
    

    我希望这对其他人有帮助 .

  • 9

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

    如果为 ItemsSource 属性指定了此属性,则应使用所有枚举值填充它 . 您可能希望将 SelectedItem 绑定到 EffectStyle (假设它是同一枚举的属性,并包含当前值) .

  • 0

    上述所有帖子都错过了一个简单的伎俩 . 可以从SelectedValue的绑定中找出如何自动填充ItemsSource以使您的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的实现如下,但首先我需要一个小帮手来获取我的枚举字符串和值

    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>
    

    就是这样 . 这显然可以扩展到支持i18n,但会使帖子更长 .

  • 8
    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));
        }
    }
    

    你应该用这种方式扩展Rogers和Greg的答案枚举值转换器,如果您直接绑定到枚举对象模型属性 .

  • 18

    通用应用似乎有点不同;它没有全功能XAML的全部功能 . 对我有用的是:

    • 我创建了一个枚举值列表作为枚举(未转换为字符串或整数)并将ComboBox ItemsSource绑定到

    • 然后我可以将ComboBox ItemSelected绑定到我的公共属性,其类型是有问题的枚举

    只是为了好玩,我掀起了一个小模板课来帮助解决这个问题并将其发布到MSDN Samples pages . 额外的位让我可以选择覆盖枚举的名称,让我隐藏一些枚举 . 我的代码看起来很像Nick's(上图),我希望我之前看到过 .

    Running the sample; it includes multiple twoway bindings to the enum

  • 41

    如果您绑定到ViewModel上的实际枚举属性,而不是枚举的int表示,则事情变得棘手 . 我发现有必要绑定到字符串表示,而不是所有上述示例中所期望的int值 .

    您可以通过将简单文本框绑定到要在ViewModel上绑定到的属性来判断是否是这种情况 . 如果显示文本,则绑定到字符串 . 如果显示数字,则绑定到该值 . 注意我已经使用了两次Display,这通常是一个错误,但它是唯一的工作方式 .

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

    格雷格

  • 0

    尼克的解决方案可以简化得更多,没什么特别的,你只需要一个转换器:

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

    然后,您可以在任何希望显示组合框的地方使用它:

    <ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />
    
  • 1

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

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

    上面引用的mashed up标记扩展的源代码:

    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("_", " ");
        }
    }
    
  • 253

    这个问题有很多优秀的答案,我谦卑地提出我的问题 . 我发现我的更简单,更优雅 . 它只需要一个值转换器 .

    鉴于枚举......

    public enum ImageFormat
    {
        [Description("Windows Bitmap")]
        BMP,
        [Description("Graphics Interchange Format")]
        GIF,
        [Description("Joint Photographic Experts Group Format")]
        JPG,
        [Description("Portable Network Graphics Format")]
        PNG,
        [Description("Tagged Image Format")]
        TIFF,
        [Description("Windows Media Photo Format")]
        WDP
    }
    

    和一个 Value 转换器......

    public class ImageFormatValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is ImageFormat format)
            {
                return GetString(format);
            }
    
            return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string s)
            {
                return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':')));
            }
            return null;
        }
    
        public string[] Strings => GetStrings();
    
        public static string GetString(ImageFormat format)
        {
            return format.ToString() + ": " + GetDescription(format);
        }
    
        public static string GetDescription(ImageFormat format)
        {
            return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute<DescriptionAttribute>().Description;
    
        }
        public static string[] GetStrings()
        {
            List<string> list = new List<string>();
            foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
            {
                list.Add(GetString(format));
            }
    
            return list.ToArray();
        }
    }
    

    资源...

    <local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>
    

    XAML声明......

    <ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
                  SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>
    

    查看型号......

    private ImageFormat _imageFormat = ImageFormat.JPG;
        public ImageFormat Format
        {
            get => _imageFormat;
            set
            {
                if (_imageFormat != value)
                {
                    _imageFormat = value;
                    OnPropertyChanged();
                }
            }
        }
    

    结果组合框......

    ComboBox bound to enum

  • 2

    使用 ReactiveUI ,我've created the following alternate solution. It'不是一个优雅的一体化解决方案,但我认为至少它是可读的 .

    在我的例子中,将 enum 列表绑定到控件是一种罕见的情况,因此我不需要跨代码库扩展解决方案 . 但是,通过将 EffectStyleLookup.Item 更改为 Object ,可以使代码更通用 . 我用我的代码测试了它,不需要其他修改 . 这意味着一个助手类可以应用于任何 enum 列表 . 虽然这会降低其可读性 - 但是它并没有很好的响应 .

    使用以下帮助程序类:

    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" />
    

    在View中,这允许我们将原始 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) );
    });
    
  • 1

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

    所以我想出了一个更简单的方法 . 将枚举器绑定到字典 . 将该字典绑定到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
    
  • 93

    简单明了的解释:http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

    xmlns:local="clr-namespace:BindingEnums"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    

    ...

    <Window.Resources>
        <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                            ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Status"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    

    ...

    <Grid>
        <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
                  ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
    </Grid>
    

相关问题