web-dev-qa-db-ja.com

SilverlightでComboBoxを列挙型にバインドしています...

したがって、WebとStackOverflowには、コンボボックスをWPFの列挙型プロパティにバインドする方法について多くの素晴らしい答えがあります。しかし、Silverlightには、これを可能にするすべての機能がありません:(。次に例を示します。

  1. Silverlightはx:Typeをサポートしていないため、型パラメーターを受け入れる一般的な EnumDisplayer -style IValueConverterは使用できません。
  2. ObjectDataProviderは、Silverlightには存在しないため、 このアプローチ のように使用できません。
  3. Silverlightにはマークアップ拡張機能が存在しないため、#2からのリンクのコメントのようにカスタムマークアップ拡張機能を使用することはできません。
  4. オブジェクトのTypeプロパティの代わりにジェネリックを使用して#1のバージョンを実行することはできません。ジェネリックはXAMLでサポートされていないためです(そして、それらを機能させるためのハックは、すべてでサポートされていないマークアップ拡張機能に依存しますSilverlight)。

大失敗!

私が見ているように、この作品を作る唯一の方法は、

  1. ビューのコードビハインドを使用して値をComboBoxにロードし、セッター/ゲッターが変換を行う私のViewModelの文字列プロパティをチートしてバインドします。
  2. バインドする列挙型ごとにカスタムIValueConverterを作成します。

より一般的な代替手段はありますか?つまり、必要な列挙型ごとに同じコードを何度も書く必要はありませんか?列挙型を型パラメーターとして受け入れるジェネリッククラスを使用してソリューション#2を実行し、必要な列挙型ごとに新しいクラスを作成することができると思います

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

皆さん、どう思いますか?

38
Domenic

ああ、私はあまりにも早く話しました! 完全に優れた解決策 があります。少なくともSilverlight 3にあります(- this thread は、Silverlightでこの問題に関連するバグが修正されたことを示しているため、3にしか存在しない可能性があります) 3.)

基本的に、ItemsSourceプロパティには単一のコンバーターが必要ですが、タイプがMyEnumであるプロパティの名前を渡せば、禁止されているメソッドを使用せずに完全に汎用的にすることができます。そして、SelectedItemへのデータバインディングは完全に簡単です。コンバーターは必要ありません!まあ、少なくともそれはあなたが各列挙値にカスタム文字列を必要としない限りです。 DescriptionAttribute、hmm ...はおそらくそのための別のコンバーターが必要になります。私がそれをジェネリックにできることを願っています。

更新:私はコンバーターを作りました、そしてそれは動作します!悲しいことに、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遅いので、キャッシュの使用を検討する必要がありますか?

34
Domenic

選択したアイテムのカスタムコンバーターを必要とせずにComboBoxを列挙型にバインドする別の方法があります。あなたはそれをチェックすることができます

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

それはDescriptionAttributesを使用しません....しかし、それは私にとっては完璧に機能するので、使用されるシナリオに依存すると思います

4
André Matos

Enumデータを単純にカプセル化する方が使いやすいことがわかりました。

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
Micael Levesque

これは、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;
        }
    }
}
0
David Hayes