在Silverlight中将ComboBox绑定到枚举类型!

38

因此,网络上和StackOverflow上有很多关于如何将WPF中的ComboBox绑定到枚举属性的不错答案。但是Silverlight缺少使其成为可能的所有功能 :( 例如:

  1. 您无法使用通用的EnumDisplayer风格的接受类型参数的IValueConverter,因为Silverlight不支持x:Type
  2. 您无法像这种方法中那样使用ObjectDataProvider,因为它在Silverlight中不存在。
  3. 您无法像#2链接中的评论中那样使用自定义标记扩展,因为标记扩展在Silverlight中不存在。
  4. 您无法使用泛型而不是对象的Type属性来执行#1的版本,因为XAML不支持泛型(并且使它们工作的各种黑客都依赖于标记扩展,在Silverlight中不受支持)。

太失败了!

我认为让它起作用的唯一方法是要么

  1. 欺骗并绑定到ViewModel中的字符串属性,其setter/getter执行转换,在View中使用代码后台加载ComboBox中的值。
  2. 为我想要绑定的每个枚举制作自定义IValueConverter

是否有更通用的替代方案,即不涉及为每个我想要的枚举编写相同的代码?我认为我可以使用接受枚举作为类型参数的通用类来执行解决方案#2,然后为我想要的每个枚举创建新类,这些类只是简单的类。

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

你们有什么想法,大家?

4个回答

34

糟糕,我说得太早了!至少在Silverlight 3中有一个完美的解决方案。(可能只在3中,因为这个线程表明与此相关的错误已在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}}" />

And this sort of resource-declaration 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>

非常感谢您的评论,因为我对XAML/Silverlight/WPF等方面还很新手。例如,EnumToIntConverter.ConvertBack 会很慢吗?我是否应该考虑使用缓存?


一定要缓存您使用Type对象(即GetFields())执行的所有操作,因为它是反射并且通常被认为很慢(当然这取决于您的应用程序对反射的使用)。除此之外,做得很好! - James Cadd
3
如果枚举值的编号不是从0开始顺序编号,那么该解决方案将失败。它依赖于枚举值和组合框索引之间的一一对应关系。否则,这是一个不错的解决方案。 - Martin Liversage
谢谢你指出这个问题,Martin!看起来还是可以解决的,如果我在“GetValues”或其他地方查找该值所在的索引。我会更新答案。 - Domenic
EnumToIntConverter.ConvertBack() 中,你可以使用 return Enum.ToObject (targetType, value); 来返回值,而不是通过缓慢的字符串转换和解析。 - Duckboy
+1 分钟为您解决问题,并将其发布供他人受益! - davidsleeps
显示剩余2条评论

4
我发现对枚举数据进行简单的封装可以更加便于使用。
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}" />

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

这只是手动创建一个仅适用于特定枚举的转换器。 - Domenic

4

有用的链接,但最好在答案中包含重要部分,以防链接失效。 - Fedor

0

这是一个适用于Windows 8.1 / Windows Phone通用应用程序的相同设置,主要更改如下:

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

似乎XAML的顺序也很重要,我必须在SelectedIndex之前放置ItemsSource,否则它不会调用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;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接