Binding ComboBoxes to enums… in Silverlight!

后端 未结 4 651
醉梦人生
醉梦人生 2021-01-31 15:42

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

相关标签:
4条回答
  • 2021-01-31 15:54

    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... :)

    0 讨论(0)
  • 2021-01-31 15:58

    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

    0 讨论(0)
  • 2021-01-31 16:05

    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;
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-31 16:11

    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?

    0 讨论(0)
提交回复
热议问题