将 ComboBoxes 绑定到枚举......在 Silverlight 中!

Posted

技术标签:

【中文标题】将 ComboBoxes 绑定到枚举......在 Silverlight 中!【英文标题】:Binding ComboBoxes to enums... in Silverlight! 【发布时间】:2010-11-19 20:53:27 【问题描述】:

因此,对于如何将组合框绑定到 WPF 中的枚举属性,Web 和 *** 有很多很好的答案。但是 Silverlight 缺少使这成为可能的所有功能 :(。例如:

    您不能使用接受类型参数的通用 EnumDisplayer 样式 IValueConverter,因为 Silverlight 不支持 x:Type。 您不能像在this approach 中那样使用ObjectDataProvider,因为它在 Silverlight 中不存在。 您不能像 #2 中链接上的 cmets 那样使用自定义标记扩展,因为 Silverlight 中不存在标记扩展。 您不能使用泛型而不是对象的 Type 属性来创建 #1 版本,因为 XAML 不支持泛型(并且使它们工作的技巧都依赖于标记扩展,在银光)。

大失败!

在我看来,完成这项工作的唯一方法是

    欺骗并绑定到我的 ViewModel 中的字符串属性,它的 setter/getter 进行转换,使用 View 中的代码隐藏将值加载到 ComboBox。 为我要绑定的每个枚举创建一个自定义 IValueConverter

有没有更通用的替代方案,即不涉及为我想要的每个枚举一遍又一遍地编写相同的代码?我想我可以使用一个接受枚举作为类型参数的泛型类来执行解决方案#2,然后为我想要的每个枚举创建新的类

class MyEnumConverter : GenericEnumConverter<MyEnum> 

各位,你们有什么想法?

【问题讨论】:

【参考方案1】:

啊,我说得太早了!至少在 Silverlight 3 中有a perfectly good solution。(它可能只在 3 中,因为this thread 表示与此内容相关的错误已在 Silverlight 3 中修复。)

基本上,您需要一个用于ItemsSource 属性的转换器,但它可以完全通用而无需使用任何禁止的方法,只要您将类型为MyEnum 的属性的名称传递给它。与SelectedItem 的数据绑定完全没有痛苦;无需转换器!好吧,至少只要您不希望通过例如每个枚举值自定义字符串DescriptionAttribute, 嗯...可能需要另一个转换器来处理那个;希望我可以让它通用。

更新:我做了一个转换器,它可以工作了!很遗憾,我现在必须绑定到SelectedIndex,但没关系。使用这些人:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

namespace DomenicDenicola.Wpf

    public class EnumToIntConverter : IValueConverter
    
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            // Note: as pointed out by Martin in the comments on this answer, this line
            // depends on the enum values being sequentially ordered from 0 onward,
            // since combobox indices are done that way. A more general solution would
            // probably look up where in the GetValues array our value variable
            // appears, then return that index.
            return (int)value;
        

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            return Enum.Parse(targetType, value.ToString(), true);
        
    
    public class EnumToIEnumerableConverter : IValueConverter
    
        private Dictionary<Type, List<object>> cache = new Dictionary<Type, List<object>>();

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            var type = value.GetType();
            if (!this.cache.ContainsKey(type))
            
                var fields = type.GetFields().Where(field => field.IsLiteral);
                var values = new List<object>();
                foreach (var field in fields)
                
                    DescriptionAttribute[] a = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    if (a != null && a.Length > 0)
                    
                        values.Add(a[0].Description);
                    
                    else
                    
                        values.Add(field.GetValue(value));
                    
                
                this.cache[type] = values;
            

            return this.cache[type];
        

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
            throw new NotImplementedException();
        
    

使用这种绑定 XAML:

<ComboBox x:Name="MonsterGroupRole"
          ItemsSource="Binding MonsterGroupRole,
                                Mode=OneTime,
                                Converter=StaticResource EnumToIEnumerableConverter"
          SelectedIndex="Binding MonsterGroupRole,
                                  Mode=TwoWay,
                                  Converter=StaticResource EnumToIntConverter" />

还有这种资源声明 XAML:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ddwpf="clr-namespace:DomenicDenicola.Wpf">
    <Application.Resources>
        <ddwpf:EnumToIEnumerableConverter x:Key="EnumToIEnumerableConverter" />
        <ddwpf:EnumToIntConverter x:Key="EnumToIntConverter" />
    </Application.Resources>
</Application>

任何 cmets 都将不胜感激,因为我有点像 XAML/Silverlight/WPF/等。新手。比如EnumToIntConverter.ConvertBack会不会很慢,所以我应该考虑使用缓存?

【讨论】:

绝对缓存您对 Type 对象(即 GetFields())所做的所有事情,因为它是反射并且通常被认为很慢(当然这取决于您的应用程序对反射的使用)。除了出色的工作! 非常有帮助。谢谢。你有没有考虑过扩展它以轻松转换值 - 例如 OrderStatus.NewOrder 到“新订单”? 确实,上面的代码会解析你添加到枚举字段中的任何DescriptionAttributes :)。 如果枚举的值没有从 0 开始按顺序编号,则此解决方案将失败。它取决于组合框中的枚举值和索引之间的一一对应关系。否则是一个不错的解决方案。 感谢您指出这一点,马丁!不过,如果我在索引上查找该值出现在GetValues 或其他任何内容中,它似乎是可以修复的。我会更新答案。【参考方案2】:

还有另一种方法可以将 ComboBox 绑定到枚举,而无需为所选项目使用自定义转换器。你可以去看看

http://charlass.wordpress.com/2009/07/29/binding-enums-to-a-combobbox-in-silverlight/

它不使用 DescriptionAttributes.... 但它对我来说非常有效,所以我想这取决于它将使用的场景

【讨论】:

有用的链接,但最好在答案中包含重要部分,以防链接损坏。【参考方案3】:

我发现枚举数据的简单封装更易于使用。

public ReadOnly property MonsterGroupRole as list(of string)
  get
    return [Enum].GetNames(GetType(GroupRoleEnum)).Tolist
  End get
End Property

private _monsterEnum as GroupRoleEnum
Public Property MonsterGroupRoleValue as Integer
  get
    return _monsterEnum
  End get
  set(value as integer)
    _monsterEnum=value
  End set
End Property

...

<ComboBox x:Name="MonsterGroupRole"
      ItemsSource="Binding MonsterGroupRole,
                            Mode=OneTime"
      SelectedIndex="Binding MonsterGroupRoleValue ,
                              Mode=TwoWay" />

这将完全消除对转换器的需求... :)

【讨论】:

大声笑,这只是手动创建一个仅适用于一个特定枚举的转换器。【参考方案4】:

这里是 Windows 8.1/Windows Phone 通用应用程序的相同设置,主要变化是:-

框架中缺少DescriptionAttribute(或者至少我找不到) 反射工作方式的差异(使用 TypeInfo.Declared 字段)

似乎 XAML 的顺序也很重要,我必须将 ItemsSource 放在 SelectedIndex 之前,否则它不会调用 ItemsSource 绑定 例如

<ComboBox
ItemsSource="Binding Path=MyProperty,Mode=OneWay, Converter=StaticResource EnumToIEnumerableConverter"
SelectedIndex="Binding Path=MyProperty, Mode=TwoWay, Converter=StaticResource EnumToIntConverter" 
/>

代码如下

namespace MyApp.Converters

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Windows.UI.Xaml.Data;
    public class EnumToIntConverter : IValueConverter
    
        public object Convert(object value, Type targetType, object parameter, string language)
        
            // Note: as pointed out by Martin in the comments on this answer, this line
            // depends on the enum values being sequentially ordered from 0 onward,
            // since combobox indices are done that way. A more general solution would
            // probably look up where in the GetValues array our value variable
            // appears, then return that index.
            return (int) value;
        

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        
            return value;
        
    

    public class EnumToIEnumerableConverter : IValueConverter
    
        private readonly Dictionary<TypeInfo, List<object>> _cache = new Dictionary<TypeInfo, List<object>>();

        public object Convert(object value, Type targetType, object parameter, string language)
        
            var type = value.GetType().GetTypeInfo();
            if (!_cache.ContainsKey(type))
            
                var fields = type.DeclaredFields.Where(field => field.IsLiteral);
                var values = new List<object>();
                foreach (var field in fields)
                
                    var a = (DescriptionAttribute[]) field.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    if (a != null && a.Length > 0)
                    
                        values.Add(a[0].Description);
                    
                    else
                    
                        values.Add(field.GetValue(value));
                    
                
                _cache[type] = values;
            
            return _cache[type];
        

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        
            throw new NotImplementedException();
        
    
    [AttributeUsage(AttributeTargets.Field)]
    public class DescriptionAttribute : Attribute
    
        public string Description  get; private set; 

        public DescriptionAttribute(string description)
        
            Description = description;
        
    

【讨论】:

以上是关于将 ComboBoxes 绑定到枚举......在 Silverlight 中!的主要内容,如果未能解决你的问题,请参考以下文章

ListBox 中的所有 ComboBox 在其中任何一个更改时都会更改

WPF 触发器绑定:将枚举值绑定到可见性的最佳方法是啥?

Winforms 将枚举绑定到单选按钮

WPF 将枚举列表(或类似列表)绑定到复选框列表

WPF 将 ListBox 绑定到枚举,显示描述属性

如何将数字动态绑定到枚举