web-dev-qa-db-fra.com

Liaison de données WPF: comment lier des données une énumération à une zone de liste déroulante à l'aide de XAML?

J'ai un cours:

public class AccountDetail
{
    public DetailScope Scope
    {
        get { return scope; }
        set { scope = value; }
    }

    public string Value
    {
        get { return this.value; }
        set { this.value = value; }
    }

    private DetailScope scope;
    private string value;

    public AccountDetail(DetailScope scope, string value)
    {
        this.scope = scope;
        this.value = value;
    }
}

et une énumération:

public enum DetailScope
{
    Private, 
    Business, 
    OtherDetail
}

Enfin, j'ai un fichier .xaml:

<Window x:Class="Gui.Wpf.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Test" 
    SizeToContent="WidthAndHeight">

    <Grid>
        <ComboBox 
            Name="ScopeComboBox" 
            Width="120" 
            Height="23" 
            Margin="12" />
    </Grid>
</Window>

Je voudrais faire deux choses:

  1. Je souhaite lier les données DetailsScope les valeurs d'énumération aux valeurs de la zone de liste déroulante. Je ne souhaite pas lier directement les valeurs d'énumération car la dernière valeur d'énumération serait OtherDetail au lieu de Other detail (a ajouté un espace et une lettre minuscule 'd').
  2. Je souhaite lier les données de la valeur sélectionnée dans la zone de liste déroulante à celle spécifiée dans l'instance de l'objet AccountDetail.

Pourrais-tu m'aider? Merci.

Mise à jour: j'ai trouvé ce message http://blogs.msdn.com/b/wpfsdk/archive/2007/02/22/displaying-enum-values-using-data-binding.aspx . J'ai besoin de quelque chose de similaire.

24
Boris

Un moyen assez simple de le faire est d'utiliser un ObjectDataProvider

<ObjectDataProvider MethodName="GetValues"
                    ObjectType="{x:Type sys:Enum}"
                    x:Key="DetailScopeDataProvider">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:DetailScope" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

Utilisez l'ObjectDataProvider comme ItemSource pour le ComboBox, liez SelectedItem à la propriété Scope et appliquez un convertisseur pour l'affichage de chaque ComboBoxItem

<ComboBox Name="ScopeComboBox"
          ItemsSource="{Binding Source={StaticResource DetailScopeDataProvider}}"
          SelectedItem="{Binding Scope}"
          Width="120"
          Height="23"
          Margin="12">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource CamelCaseConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Et dans le convertisseur, vous pouvez utiliser Regex pour le séparateur de chaînes CamelCase trouvé dans la question this . J'ai utilisé la version la plus avancée mais vous pouvez probablement en utiliser une plus simple. OtherDetail + the regex = Other Detail. Rendre la valeur de retour inférieure puis renvoyer une chaîne avec le premier caractère UpperCase devrait vous donner le résultat attendu

public class CamelCaseConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string enumString = value.ToString();
        string camelCaseString = Regex.Replace(enumString, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ").ToLower();
        return char.ToUpper(camelCaseString[0]) + camelCaseString.Substring(1);
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }
}
41
Fredrik Hedblad

La façon dont je l'ai toujours fait est la suivante. La beauté de cette solution est qu'elle est complètement générique et peut être réutilisée pour tous les types d'énumération.

1) Lorsque vous définissez une énumération, utilisez certains attributs personnalisés pour donner un peu d'informations. Dans cet exemple, j'ai utilisé Browsable (false) pour indiquer que cet énumérateur est interne et je ne veux pas voir cette option dans une zone de liste déroulante. La description ("") me permet de spécifier un nom d'affichage pour l'énumération.

public enum MyEnumerationTypeEnum
  {
    [Browsable(false)]
    Undefined,
    [Description("Item 1")]
    Item1,
    [Description("Item 2")]
    Item2,
    Item3
  }

2) Définissez une classe que j'ai appelée EnumerationManager. Il s'agit d'une classe générique qui analyse un type d'énumération et génère une liste de valeurs. Si un énumérateur a défini Browsable sur false, il sera ignoré. S'il a un attribut Description, il utilisera la chaîne de description comme nom d'affichage. Si aucune description n'est trouvée, elle affichera simplement la chaîne par défaut de l'énumérateur.

public class EnumerationManager
  {
    public static Array GetValues(Type enumeration)
    {
      Array wArray = Enum.GetValues(enumeration);
      ArrayList wFinalArray = new ArrayList();
      foreach(Enum wValue in wArray)
      {
        FieldInfo fi = enumeration.GetField(wValue.ToString());
        if(null != fi)
        {
          BrowsableAttribute[] wBrowsableAttributes = fi.GetCustomAttributes(typeof(BrowsableAttribute),true) as BrowsableAttribute[];
          if(wBrowsableAttributes.Length > 0)
          {
            //  If the Browsable attribute is false
            if(wBrowsableAttributes[0].Browsable == false)
            {
              // Do not add the enumeration to the list.
              continue;
            }        
          }

          DescriptionAttribute[] wDescriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute),true) as DescriptionAttribute[];
      if(wDescriptions.Length > 0)
      {
        wFinalArray.Add(wDescriptions[0].Description);
      }
      else 
        wFinalArray.Add(wValue);
        }
      }

      return wFinalArray.ToArray();
    }
  }

3) Dans votre xaml ajoutez une section dans votre ResourceDictionary

  <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type l:EnumerationManager}" x:Key="OutputListForMyComboBox">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="l:MyEnumerationTypeEnum" />
      </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>

4) Maintenant, liez simplement le ItemsSource de votre combobox à cette clé que nous venons de définir dans le dictionnaire de ressources

<ComboBox Name="comboBox2" 
          ItemsSource="{Binding Source={StaticResource OutputListForMyComboBox}}" />

Si vous essayez ce code en utilisant mon énumération ci-dessus, vous devriez voir 3 éléments dans votre zone de liste déroulante:

Item 1
Item 2
Item3

J'espère que cela t'aides.

EDITS: Si vous ajoutez l'implémentation de LocalizableDescriptionAttribute et que vous l'utilisez à la place, l'attribut Description que j'utilisais serait parfait.

11
Liz

Voici une solution: vous créez une propriété (une liste) qui contient toutes les possibilités et vous liez votre ComboBox sur cette propriété.

Dans le XAML:

<ComboBox
    Name="ScopeComboBox" 
    Width="120" 
    Height="23" 
    Margin="12" 
    ItemsSource="{Binding Path=AccountDetailsProperty}"
    DisplayMemberPath="Value"/>

Et dans le code derrière:

public partial class Window1 : Window
{
    public Window1() 
    {
        AccountDetailsProperty = new List<AccountDetail>()
        {
            new AccountDetail(DetailScope.Business, "Business"),
            new AccountDetail(DetailScope.OtherDetail, "Other details"),
            new AccountDetail(DetailScope.Private, "Private"),
        };

        InitializeComponent();
        this.DataContext = this;
    }

    public List<AccountDetail> AccountDetailsProperty { get; set; }
}
2
Nicolas

J'utiliserais un convertisseur de valeur pour cela, cela vous permettra de lier directement en utilisant le convertisseur, vous pouvez changer l'implémentation Convert pour créer une présentation lisible par l'homme plus agréable des énumérations, c'est-à-dire divisée en caractères majuscules.

Il y a un article complet sur cette approche ici .

  public class MyEnumToStringConverter : IValueConverter
  {
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
         return value.ToString();
     }

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
         return (MyEnum) Enum.Parse( typeof ( MyEnum ), value.ToString(), true );
     }
  }
1
BrokenGlass