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

时间:2009-08-15 08:58:43

标签: silverlight data-binding xaml enums

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

  1. 您不能使用接受类型参数的通用EnumDisplayer - 样式IValueConverter,因为Silverlight不支持x:Type
  2. 您不能像this approach一样使用ObjectDataProvider,因为它在Silverlight中不存在。
  3. 您不能像#2链接中的注释那样使用自定义标记扩展名,因为Silverlight中不存在标记扩展名。
  4. 您不能使用泛型而不是对象的Type属性来执行#1版本,因为XAML中不支持泛型(并且使它们工作的hacks全部依赖于标记扩展,而不是Silverlight支持。)
  5. 大规模失败!

    正如我所看到的,实现这项工作的唯一方法是

    1. 作弊并绑定到我的ViewModel中的字符串属性,其setter / getter执行转换,使用View中的代码隐藏将值加载到ComboBox中。
    2. 为我想要绑定的每个枚举制作一个自定义IValueConverter
    3. 是否有更通用的替代方案,即不要为我想要的每个枚举反复编写相同的代码?我想我可以使用一个接受enum作为类型参数的泛型类来做解决方案#2,然后为我想要的每个枚举创建新类

      class MyEnumConverter : GenericEnumConverter<MyEnum> {}
      

      你有什么想法?伙计们?

4 个答案:

答案 0 :(得分:34)

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

基本上,你需要一个ItemsSource属性的转换器,但它可以完全通用而不使用任何禁止的方法,只要你传递一个类型为{{1的属性的名称}}。对MyEnum的数据绑定完全没有痛苦;不需要转换器!好吧,至少只要您不想通过例如每个枚举值自定义字符串。 SelectedItem,嗯...可能需要另一个转换器;希望我能把它变成通用的。

更新:我制作了转换器,它可以正常工作!遗憾的是,我现在必须绑定到DescriptionAttribute,但是没关系。使用这些家伙:

SelectedIndex

使用这种绑定XAML:

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 / Silverlight / WPF /等。新手。例如,<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> 会慢,所以我应该考虑使用缓存吗?

答案 1 :(得分:4)

还有另一种方法可以将ComboBox绑定到枚举,而无需为所选项目定制转换器。你可以在

查看

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

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

答案 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}" />

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

答案 3 :(得分:0)

以下是Windows 8.1 / Windows Phone通用应用程序的相同设置,主要更改如下: -

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

似乎XAML的顺序也很重要,我不得不将ItemsSource放在SelectedIndex之前,否则它不会调用ItemsSource绑定 e.g。

<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;
        }
    }
}