201 votes

Comment lier une énumération à un contrôle combobox dans WPF ?

J'essaie de trouver un exemple simple où les enums sont affichés tels quels. Tous les exemples que j'ai vus essaient d'ajouter de belles chaînes d'affichage, mais je ne veux pas de cette complexité.

En fait, j'ai une classe qui contient toutes les propriétés que je lie, en fixant d'abord le DataContext à cette classe, puis en spécifiant la liaison comme ceci dans le fichier xaml :

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

Mais cela n'affiche pas les valeurs de l'énumération dans le tableau de bord. ComboBox comme des articles.

12 votes

Voici ce que vous recherchez : WPF ObjectDataProvider - Lier une Enum à une ComboBox Vous pouvez également y télécharger le code source complet de l'exemple.

0 votes

La meilleure réponse, à mon avis, est dans : stackoverflow.com/questions/58743/

0 votes

336voto

kirmir Points 3825

Vous pouvez le faire à partir du code en plaçant le code suivant dans Window Loaded le gestionnaire d'événements, par exemple :

yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();

Si vous avez besoin de le lier dans XAML, vous devez utiliser ObjectDataProvider pour créer un objet disponible comme source de liaison :

<Window x:Class="YourNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:StyleAlias="clr-namespace:Motion.VideoEffects">
    <Window.Resources>
        <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                            ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="StyleAlias:EffectStyle"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
                  SelectedItem="{Binding Path=CurrentEffectStyle}" />
    </Grid>
</Window>

Attirez l'attention sur le code suivant :

xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"

Guide sur la façon de mettre en correspondance les espaces de noms et les assemblages. MSDN .

0 votes

Merci, cela semble être le cas. Pouvez-vous s'il vous plaît montrer ceci dans le xaml ?

0 votes

@Joan Venge, pour le lier dans XAML vous devez utiliser ObjectDataProvider comme décrit dans l'exemple donné par raj dans une autre réponse.

0 votes

Merci mais cet échantillon ne fonctionne pas. J'obtiens cette erreur The property 'MethodParameters' is read-only and cannot be changed.

136voto

Nick Points 976

J'aimerais que tous les objets que je lie soient définis dans mon ViewModel J'essaie donc d'éviter d'utiliser <ObjectDataProvider> dans le xaml lorsque cela est possible.

Ma solution n'utilise aucune donnée définie dans la vue et aucun code-behind. Seulement un DataBinding, un ValueConverter réutilisable, une méthode pour obtenir une collection de descriptions pour n'importe quel type d'Enum, et une seule propriété dans le ViewModel à lier.

Lorsque je veux lier un Enum à un ComboBox le texte que je veux afficher ne correspond jamais aux valeurs des éléments suivants Enum donc j'utilise le [Description()] pour lui donner le texte que je souhaite voir apparaître dans l'écran. ComboBox . Si j'avais un enum des jours de la semaine, il ressemblerait à quelque chose comme ceci :

public enum DayOfWeek
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Sunday")]
  SUNDAY,
  [Description("Monday")]
  MONDAY,
  ...
}

D'abord, j'ai créé une classe d'aide avec quelques méthodes pour traiter les enums. Une méthode obtient une description pour une valeur spécifique, l'autre méthode obtient toutes les valeurs et leurs descriptions pour un type.

public static class EnumHelper
{
  public static string Description(this Enum value)
  {
    var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
    if (attributes.Any())
      return (attributes.First() as DescriptionAttribute).Description;

    // If no description is found, the least we can do is replace underscores with spaces
    // You can add your own custom default formatting logic here
    TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
    return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
  }

  public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
  {
    if (!t.IsEnum)
      throw new ArgumentException($"{nameof(t)} must be an enum type");

    return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
  }
}

Ensuite, nous créons un ValueConverter . Héritant de MarkupExtension permet de l'utiliser plus facilement dans XAML, sans avoir à le déclarer en tant que ressource.

[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
  }
  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return null;
  }
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    return this;
  }
}

Mon ViewModel n'a besoin que d'une seule propriété que mon View peuvent se lier à la fois pour le SelectedValue y ItemsSource de la combobox :

private DayOfWeek dayOfWeek;

public DayOfWeek SelectedDay
{
  get { return dayOfWeek; }
  set
  {
    if (dayOfWeek != value)
    {
      dayOfWeek = value;
      OnPropertyChanged(nameof(SelectedDay));
    }
  }
}

Et enfin, pour lier le ComboBox (en utilisant le ValueConverter en el ItemsSource de liaison)...

<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
          SelectedValuePath="Value"
          DisplayMemberPath="Description"
          SelectedValue="{Binding Path=SelectedDay}" />

Pour mettre en œuvre cette solution, il vous suffit de copier mes EnumHelper et EnumToCollectionConverter classe. Ils travailleront avec tout enums. De plus, je ne l'ai pas inclus ici, mais la fonction ValueDescription est une classe simple avec 2 propriétés d'objet publiques, l'une appelée Value un appelé Description . Vous pouvez le créer vous-même ou modifier le code afin d'utiliser un fichier Tuple<object, object> o KeyValuePair<object, object>

9 votes

Pour que cela fonctionne, j'ai dû créer une ValueDescription qui possède des propriétés publiques pour Value y Description

5 votes

Oui, vous pouvez également modifier ce code pour utiliser une Tuple<T1, T2> ou ou KeyValuePair<TKey, TValue> au lieu de la ValueDescription et vous n'auriez pas à créer votre propre classe.

0 votes

J'avais besoin d'implémenter OnPropertyChanged (ou l'équivalent) pour les deux propriétés du ViewModel, et pas seulement pour SelectedClass.

56voto

tom.maruska Points 142

J'ai utilisé une autre solution en utilisant MarkupExtension.

  1. J'ai créé une classe qui fournit la source des éléments :

    public class EnumToItemsSource : MarkupExtension
    {
        private readonly Type _type;
    
        public EnumToItemsSource(Type type)
        {
            _type = type;
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return Enum.GetValues(_type)
                .Cast<object>()
                .Select(e => new { Value = (int)e, DisplayName = e.ToString() });
        }
    }
  2. C'est presque tout... Maintenant, utilisez-le dans XAML :

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Remplacez 'enums:States' par votre enum.

1 votes

@Nick : La réponse acceptée est de référencer l'enum (ou le modèle comme vous l'avez dit) dans le xaml aussi. Votre solution consiste à créer 2 propriétés et un champ de sauvegarde dans le modèle de vue, ce qui ne me plaît pas (règle DRY). Et bien sûr, vous n'avez pas besoin d'utiliser e.ToString() pour le nom d'affichage. Vous pouvez utiliser votre propre traducteur, analyseur d'attributs de descrtiption, ou autre.

2 votes

@tom.maruska Je n'essaie pas d'opposer ma réponse à la vôtre, mais puisque vous en parlez, le fait d'avoir 2 propriétés ne viole pas la règle DRY lorsqu'il s'agit de 2 propriétés distinctes qui servent des objectifs différents. Et votre réponse nécessiterait également l'ajout d'une propriété (vous avez même appelé cette propriété vous-même). {Binding Path=WhereEverYouWant} ) et si vous voulez qu'il prenne en charge la liaison bidirectionnelle, vous devrez également disposer d'un champ arrière pour cela. Ainsi, vous ne remplacez pas deux propriétés et un champ d'accompagnement, mais seulement une propriété en lecture seule d'une ligne.

0 votes

Nick Oui, vous avez raison au sujet de cette propriété et du champ de sauvegarde :)

13voto

Roger Points 31

La réponse de Nick m'a vraiment aidé, mais je me suis rendu compte qu'elle pouvait être légèrement modifiée, pour éviter une classe supplémentaire, ValueDescription. Je me suis souvenu qu'il existe déjà une classe KeyValuePair dans le framework, qui peut donc être utilisée à la place.

Le code ne change que légèrement :

public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("TEnum must be an Enumeration type");
        }

        return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
               select new KeyValuePair<string, string>(e.ToString(),  e.Description());
    }

public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
   get
   {
       return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
   }
}

et enfin le XAML :

<ComboBox ItemSource="{Binding Path=PlayerClassList}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=SelectedClass}" />

J'espère que cela sera utile à d'autres.

0 votes

Ma première mise en œuvre a utilisé un KeyValuePair mais j'ai finalement décidé d'utiliser un KeyValuePair pour représenter quelque chose qui n'est pas une paire clé-valeur juste pour éviter d'écrire une classe trivialement simple n'avait pas beaucoup de sens. Le site ValueDescription n'est que de 5 lignes, dont 2 sont juste des { y }

8voto

Andy Points 15910

Vous devrez créer un tableau des valeurs de l'enum, qui peut être créé en appelant System.Enum.GetValues() en lui transmettant le Type de l'enum dont vous voulez les éléments.

Si vous le spécifiez pour l'option ItemsSource alors elle doit être remplie avec toutes les valeurs de l'enum. Vous voulez probablement lier SelectedItem a EffectStyle (en supposant qu'il s'agit d'une propriété du même enum, et qu'elle contient la valeur actuelle).

0 votes

Merci, pouvez-vous montrer la première partie en code s'il vous plaît ? Je ne suis pas sûr de l'endroit où stocker les valeurs de l'enum en tant que tableau ? La propriété de l'enum est située dans une autre classe. Puis-je faire cette étape GetValues dans le xaml ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X