将组合框绑定到枚举...在 Silverlight 中!

发布于 2024-08-02 12:11:48 字数 1107 浏览 3 评论 0原文

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

  1. 您不能使用通用的 EnumDisplayer-样式 IValueConverter 接受类型参数,因为 Silverlight 不支持 < code>x:Type
  2. 不能使用 ObjectDataProvider,如 此方法,因为它在 Silverlight 中不存在。
  3. 您不能像注释中那样使用自定义标记扩展在 #2 的链接上,因为 Silverlight 中不存在标记扩展,因此
  4. 您无法使用泛型而不是对象的 Type 属性来执行 #1 的版本,因为不支持泛型。在 XAML 中(并且使它们工作的技巧全部依赖于标记扩展,在 Silverlight 中不受支持)

欺骗并绑定到我的 ViewModel 中的字符串

正如我所见,使这项工作有效的唯一方法是

  1. 属性 。 ,其 setter/getter 执行转换,使用视图中的代码隐藏将值加载到 ComboBox 中。
  2. 为我想要绑定的每个枚举创建一个自定义的IValueConverter

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

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

你们的想法,伙计们?

So, the web, and StackOverflow, have plenty of nice answers for how to bind a combobox to an enum property in WPF. But Silverlight is missing all of the features that make this possible :(. For example:

  1. You can't use a generic EnumDisplayer-style IValueConverter that accepts a type parameter, since Silverlight doesn't support x:Type.
  2. You can't use ObjectDataProvider, like in this approach, since it doesn't exist in Silverlight.
  3. You can't use a custom markup extension like in the comments on the link from #2, since markup extensions don't exist in Silverlight.
  4. You can't do a version of #1 using generics instead of Type properties of the object, since generics aren't supported in XAML (and the hacks to make them work all depend on markup extensions, not supported in Silverlight).

Massive fail!

As I see it, the only way to make this work is to either

  1. Cheat and bind to a string property in my ViewModel, whose setter/getter does the conversion, loading values into the ComboBox using code-behind in the View.
  2. Make a custom IValueConverter for every enum I want to bind to.

Are there any alternatives that are more generic, i.e. don't involve writing the same code over and over for every enum I want? I suppose I could do solution #2 using a generic class accepting the enum as a type parameter, and then create new classes for every enum I want that are simply

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

What are your thoughts, guys?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

孤独岁月 2024-08-09 12:11:48

啊,我说得太早了!有一个完美的解决方案,至少在 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}}" />

以及这种资源声明 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 会很慢吗?我应该考虑使用缓存吗?

Agh, I spoke too soon! There is a perfectly good solution, at least in Silverlight 3. (It might only be in 3, since this thread indicates that a bug related to this stuff was fixed in Silverlight 3.)

Basically, you need a single converter for the ItemsSource property, but it can be entirely generic without using any of the prohibited methods, as long as you pass it the name of a property whose type is MyEnum. And databinding to SelectedItem is entirely painless; no converter needed! Well, at least it is as long as you don't want custom strings for each enum value via e.g. the DescriptionAttribute, hmm... will probably need another converter for that one; hope I can make it generic.

Update: I made a converter and it works! I have to bind to SelectedIndex now, sadly, but it's OK. Use these guys:

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

With this sort of binding 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>

Any comments would be appreciated, as I'm somewhat of a XAML/Silverlight/WPF/etc. newbie. For example, will the EnumToIntConverter.ConvertBack be slow, so that I should consider using a cache?

南街女流氓 2024-08-09 12:11:48

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

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

它不使用 DescriptionAttributes....但它非常适合我,所以我想这取决于它的使用场景

There is another way to bind ComboBox to enums without the need of a custom converter for the selected item. You can check it at

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

It doesn't use the DescriptionAttributes.... but it works perfectly for me, so i guess it depends on the scenario it will be used

半夏半凉 2024-08-09 12:11:48

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

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

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

I find that a simple encapsulation of enum data is way easier to use.

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

And this will completly remove the need of a converter... :)

病毒体 2024-08-09 12:11:48

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

Here is the same setup for a Windows 8.1/Windows Phone universal App, main changes are:-

  • Missing DescriptionAttribute in the framework (or at least I can't find it)
  • Differences in how reflection works (using TypeInfo.Declared fields)

It seems that the order of the XAML is important too, I had to put ItemsSource before SelectedIndex otherwise it didn't call the ItemsSource binding
e.g.

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

Code below

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文