web-dev-qa-db-fra.com

Binding ComboBoxes à Enums ... en Silverlight!

Ainsi, le Web et Stackoverflow, ont de nombreuses réponses de bonnes réponses pour la manière de lier une combinaison à une propriété Enum dans WPF. Mais Silverlight manque toutes les fonctionnalités qui rendent cela possible :(. Par exemple:

  1. Vous ne pouvez pas utiliser de générique EnumDisplayer - style IValueConverter qui accepte un paramètre de type, car Silverlight ne prend pas en charge x:Type.
  2. Vous ne pouvez pas utiliser ObjectDataProvider, comme dans cette approche , puisqu'il n'existe pas en Silverlight.
  3. Vous ne pouvez pas utiliser une extension de marquage personnalisé comme dans les commentaires sur le lien de N ° 2, car les extensions de balisage n'existent pas dans Silverlight.
  4. Vous ne pouvez pas faire une version de n ° 1 à l'aide de génériques au lieu de Type propriétés de l'objet, car les génériques ne sont pas pris en charge dans XAML (et les hacks pour les faire fonctionnerent toutes dépendent des extensions de balisage, non prises en charge dans Silverlight).

FAUX MASSIVE!

Comme je le vois, le seul moyen de faire ce travail est de soit

  1. Trichez et liez une propriété à cordes dans mon point de vue, dont le réglage/getter effectue la conversion, chargant des valeurs dans la combinaison de Code-derrière dans la vue.
  2. Faites une personnalisation IValueConverter pour chaque enum que je veux lier.

Y a-t-il des alternatives plus génériques, c'est-à-dire que vous n'impliquez pas d'écrire le même code encore et plus pour chaque énumé que je veux? Je suppose que je puisse faire la solution n ° 2 à l'aide d'une classe générique acceptant l'ENUM en tant que paramètre de type, puis créez de nouvelles classes pour chaque énum que je veux simplement

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

Quelles sont vos pensées, les gars?

38
Domenic

Agh, j'ai parlé trop tôt! Il y a ne solution parfaitement bonne , au moins dans Silverlight 3. (il ne peut être que dans 3, car - ce fil indique qu'un bug lié à cette substance a été fixé à Silverlight 3.)

Fondamentalement, vous avez besoin d'un seul convertisseur pour la propriété ItemsSource, mais il peut être entièrement générique sans utiliser les méthodes interdites, tant que vous le transmettez le nom d'une propriété dont le type est MyEnum. Et databinding à SelectedItem est totalement indolore; Aucun convertisseur nécessaire! Eh bien, au moins, il est aussi long que vous ne voulez pas de chaînes personnalisées pour chaque valeur Enum via E.G. Le DescriptionAttribute, hmm ... aura probablement besoin d'un autre convertisseur pour celui-ci; J'espère que je peux le rendre générique.

Mise à jour: J'ai fait un convertisseur et ça marche! Je dois me lier à SelectedIndex maintenant, malheureusement, mais ça va. Utilisez ces gars:

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

Avec ce type de liaison xaml:

<ComboBox x:Name="MonsterGroupRole"
          ItemsSource="{Binding MonsterGroupRole,
                                Mode=OneTime,
                                Converter={StaticResource EnumToIEnumerableConverter}}"
          SelectedIndex="{Binding MonsterGroupRole,
                                  Mode=TwoWay,
                                  Converter={StaticResource EnumToIntConverter}}" />

Et ce type de déclaration de ressources 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>

Toutes les commentaires seraient appréciés, comme je suis un peu XAML/Silverlight/WPF/etc. débutant. Par exemple, le EnumToIntConverter.ConvertBack Soyez lent, de sorte que je devrais envisager d'utiliser un cache?

34
Domenic

Il existe une autre façon de lier ComboBox à Enums sans avoir besoin d'un convertisseur personnalisé pour l'élément sélectionné. Vous pouvez le vérifier à

http://charlass.wordpress.com/2009/07/29/Binding-Enums-a-a-combobbox-in-silverlight/

Il n'utilise pas la descriptionAttributes .... mais cela fonctionne parfaitement pour moi, alors je suppose que cela dépend du scénario qu'il sera utilisé.

4
André Matos

Je trouve qu'une simple encapsulation de données ENUM est la solution plus facile à utiliser.

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

Et cela éliminera complètement le besoin d'un convertisseur ... :)

4
Micael Levesque

Voici la même configuration pour une application universelle Windows 8.1/Windows Phone, les principaux changements sont les suivants: -

  • Description manquanteAttribute dans le cadre (ou au moins, je ne peux pas le trouver)
  • Différences dans la façon dont la réflexion fonctionne (à l'aide des champs TypeInfo.Declared)

Il semble que l'ordre du XAML soit également important, je devais mettre des objets à la sélection suivante, sinon cela n'a pas appelé l'objet de la liaison E.g.

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

Code ci-dessous

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