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

185

J'essaie de trouver un exemple simple où les énumérations sont affichées telles quelles. Tous les exemples que j'ai vus essaient d'ajouter de jolies chaînes d'affichage, mais je ne veux pas de cette complexité.

Fondamentalement, j'ai une classe qui contient toutes les propriétés que je lie, en définissant d'abord le DataContext sur cette classe, puis en spécifiant la liaison comme celle-ci dans le fichier xaml:

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

Mais cela ne montre pas les valeurs d'énumération dans les ComboBoxéléments as.

Joan Venge
la source
9
Voici ce que vous recherchez: WPF ObjectDataProvider - Binding Enum to ComboBox Vous pouvez également télécharger l'exemple de code source complet à partir de là.
La meilleure réponse à mon avis est dans: stackoverflow.com/questions/58743/…
gimpy
Double
UuDdLrLrSs

Réponses:

314

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

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

Si vous avez besoin de le lier en XAML, vous devez utiliser ObjectDataProviderpour 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"

Guidez comment mapper l'espace de noms et l'assembly que vous pouvez lire sur MSDN .

Kyrylo M
la source
1
Exemple testé du premier lien, fonctionne bien. Voir le code ajouté et le commentaire dans ma réponse.
Kyrylo M
1
Vous avez trouvé votre problème sur les forums MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Essayez de nettoyer et de reconstruire le projet. Vous devriez probablement demander ce problème ici sur une autre question. C'est le seul que je puisse conseiller ... Quoi qu'il en soit, l'exemple montré est correct.
Kyrylo M
1
Merci, c'est bizarre mais j'ai vu des trucs similaires avec wpf folie. Fera et vous le fera savoir. Btw est-ce le même problème décrit ici: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge
2
Vous devez y ajouter une référence et ajouter xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"du XAML pour l'utiliser. Voici le guide: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M
4
Vous pouvez utiliser des outils tels que ReSharper. Il analyse tous les assemblys référencés et donne des suggestions à inclure. Pas besoin d'écrire - il suffit de sélectionner parmi les options.
Kyrylo M
121

J'aime que tous les objets que je lie soient définis dans my ViewModel, donc j'essaie 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. Uniquement un DataBinding, un ValueConverter réutilisable, une méthode pour obtenir une collection de descriptions pour tout type Enum et une seule propriété dans le ViewModel à laquelle se lier.

Lorsque je veux lier un Enumà un, ComboBoxle texte que je veux afficher ne correspond jamais aux valeurs de Enum, donc j'utilise l' [Description()]attribut pour lui donner le texte que je veux réellement voir dans le ComboBox. Si j'avais une énumération des jours de la semaine, cela ressemblerait à ceci:

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

J'ai d'abord créé une classe d'assistance avec quelques méthodes pour gérer les énumérations. 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 fichier ValueConverter. L'héritage de MarkupExtensionfacilite son utilisation en XAML, nous n'avons donc pas à 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;
  }
}

My ViewModeln'a besoin que d'une propriété à laquelle my Viewpeut se lier à la fois pour le SelectedValueet ItemsSourcede la zone de liste déroulante:

private DayOfWeek dayOfWeek;

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

Et enfin pour lier la ComboBoxvue (en utilisant le ValueConverterdans la ItemsSourceliaison) ...

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

Pour implémenter cette solution, il vous suffit de copier ma EnumHelperclasse et ma EnumToCollectionConverterclasse. Ils fonctionneront avec toutes les énumérations. De plus, je ne l'ai pas inclus ici, mais la ValueDescriptionclasse est juste une classe simple avec 2 propriétés d'objet public, une appelée Value, une appelée Description. Vous pouvez le créer vous-même ou modifier le code pour utiliser un Tuple<object, object>ouKeyValuePair<object, object>

pseudo
la source
9
Pour faire ce travail, j'ai dû créer une ValueDescriptionclasse qui a des propriétés publiques pour ValueetDescription
Perchik
4
Oui, vous pouvez également modifier ce code pour utiliser un Tuple<T1, T2>ou ou KeyValuePair<TKey, TValue>au lieu de la ValueDescriptionclasse et vous n'aurez pas à créer le vôtre.
Nick
J'avais besoin d'implémenter OnPropertyChanged (ou l'équivalent) pour les deux propriétés ViewModel, pas seulement SelectedClass.
Will
Vous ne devriez pas avoir besoin d'implémenter OnPropertyChanged pour la propriété qui renvoie la liste. La liste est générée à partir des valeurs d'un Enum. Il ne changera jamais pendant l'exécution, et quand il ne change jamais, il n'a jamais besoin d'avertir quiconque qu'il a changé. De plus, avec la version mise à jour, la propriété list n'est même pas du tout nécessaire.
Nick
Comment ItemSource et SelectedValue de la zone de liste déroulante ont-ils la même propriété? Le ItemsSource n'a-t-il pas besoin d'être une liste? Oh, je vois, c'est parce que l'EnumHelper fait une liste d'objets. cela rend en fait mon ViewModel plus simple car je n'ai pas à maintenir une liste distincte d'objets pour remplir le ItemSource.
Stealth Rabbi
47

J'ai utilisé une autre solution 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 en XAML:

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

tom.maruska
la source
1
@Nick: La réponse acceptée fait référence à l'énumération (ou au modèle comme vous l'avez dit) en xaml également. Votre solution crée 2 propriétés et un champ de sauvegarde dans le modèle de vue, ce que je n'ai pas aimé (règle DRY). Et bien sûr, vous n'avez pas à utiliser e.ToString()pour le nom d'affichage. Vous pouvez utiliser votre propre traducteur, analyseur d'attributs de description, peu importe.
tom.maruska
2
@ tom.maruska Je n'essaye pas d'entrer dans ma réponse par rapport à votre réponse, mais depuis que vous l'avez soulevée, avoir 2 propriétés ne viole pas la règle DRY quand 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'elle prenne en charge la liaison bidirectionnelle, vous aurez également un champ de sauvegarde pour elle. Ainsi, vous ne remplacez pas 2 propriétés et 1 champ de sauvegarde en faisant cela, vous ne remplacez qu'une seule propriété en lecture seule sur une seule ligne.
Nick le
@Nick Oui, vous avez raison à propos de cette propriété et de ce champ de soutien :)
tom.maruska
26

Utilisez ObjectDataProvider:

<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

puis liez à la ressource statique:

ItemsSource="{Binding Source={StaticResource enumValues}}"

basé sur cet article

druss
la source
4
Solution parfaitement simple. Espace de noms pour le système comme dans la réponse de kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite
Fonctionne bien dans les projets WPF de Visual Studio 2017.
Sorush
12

La réponse de Nick m'a vraiment aidé, mais j'ai réalisé 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, donc cela peut être utilisé à 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 aux autres.

Roger
la source
Ma première implémentation utilisait a KeyValuePairmais à la fin j'ai décidé d'utiliser a KeyValuePairpour 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. La ValueDescriptionclasse ne compte que 5 lignes, et 2 d'entre elles sont justes {et}
Nick
8

Vous devrez créer un tableau des valeurs de l'énumération, qui peut être créé en appelant System.Enum.GetValues ​​() , en lui passant Typel'énumération dont vous voulez les éléments.

Si vous spécifiez ceci pour la ItemsSourcepropriété, elle doit être remplie avec toutes les valeurs de l'énumération. Vous souhaitez probablement établir une liaison SelectedItemavec EffectStyle(en supposant qu'il s'agit d'une propriété de la même énumération et qu'il contienne la valeur actuelle).

Andy
la source
Merci, pouvez-vous montrer la première partie du code s'il vous plaît? Je ne sais pas où stocker les valeurs d'énumération en tant que tableau? La propriété enum est située dans une autre classe. Puis-je faire cette étape GetValues ​​dans xaml?
Joan Venge
4

Tous les messages ci-dessus ont manqué une astuce simple. Il est possible à partir de la liaison de SelectedValue de savoir comment remplir le ItemsSource AUTOMAGIQUEMENT afin que votre balisage XAML soit juste.

<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>

Par exemple dans mon ViewModel j'ai

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


    FoolEnum _Fool;
    public FoolEnum Fool
    {
        get { return _Fool; }
        set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
    }

ValidateRaiseAndSetIfChanged est mon hook INPC. Le vôtre peut différer.

L'implémentation d'EnumComboBox est la suivante, mais j'aurai d'abord besoin d'un petit assistant pour obtenir mes chaînes d'énumération et mes valeurs

    public static List<Tuple<object, string, int>> EnumToList(Type t)
    {
        return Enum
            .GetValues(t)
            .Cast<object>()
            .Select(x=>Tuple.Create(x, x.ToString(), (int)x))
            .ToList();
    }

et la classe principale (notez que j'utilise ReactiveUI pour accrocher les changements de propriété via WhenAny)

using ReactiveUI;
using ReactiveUI.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Documents;

namespace My.Controls
{
    public class EnumComboBox : System.Windows.Controls.ComboBox
    {
        static EnumComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox)));
        }

        protected override void OnInitialized( EventArgs e )
        {
            base.OnInitialized(e);

            this.WhenAnyValue(p => p.SelectedValue)
                .Where(p => p != null)
                .Select(o => o.GetType())
                .Where(t => t.IsEnum)
                .DistinctUntilChanged()
                .ObserveOn(RxApp.MainThreadScheduler)
                .Subscribe(FillItems);
        }

        private void FillItems(Type enumType)
        {
            List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();

            foreach (var idx in EnumUtils.EnumToList(enumType))
            {
                values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2));
            }

            this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList();

            UpdateLayout();
            this.ItemsSource = values;
            this.DisplayMemberPath = "Value";
            this.SelectedValuePath = "Key";

        }
    }
}

Vous devez également définir correctement le style dans Generic.XAML ou votre boîte ne rendra rien et vous vous tirerez les cheveux.

<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>

et c'est ça. Cela pourrait évidemment être étendu pour prendre en charge i18n mais allongerait le poste.

bradgonesurf
la source
3

Les applications universelles semblent fonctionner un peu différemment; il n'a pas toute la puissance du XAML complet. Ce qui a fonctionné pour moi, c'est:

  1. J'ai créé une liste des valeurs d'énumération en tant qu'énumérations (non converties en chaînes ou en nombres entiers) et j'ai lié le ComboBox ItemsSource à cela
  2. Ensuite, je pourrais lier le ComboBox ItemSelected à ma propriété publique dont le type est l'énumération en question

Juste pour le plaisir, j'ai créé une petite classe basée sur des modèles pour vous aider et je l'ai publiée sur les pages MSDN Samples . Les bits supplémentaires me permettent éventuellement de remplacer les noms des énumérations et de masquer certaines des énumérations. Mon code ressemble à celui de Nick (ci-dessus), que j'aurais aimé voir plus tôt.

Exécution de l'échantillon;  il comprend plusieurs liaisons bidirectionnelles à l'énumération

PESMITH_MSFT
la source
3

Il existe de nombreuses excellentes réponses à cette question et je soumets humblement la mienne. Je trouve que le mien est un peu plus simple et plus élégant. Il ne nécessite qu'un convertisseur de valeur.

Étant donné une énumération ...

public enum ImageFormat
{
    [Description("Windows Bitmap")]
    BMP,
    [Description("Graphics Interchange Format")]
    GIF,
    [Description("Joint Photographic Experts Group Format")]
    JPG,
    [Description("Portable Network Graphics Format")]
    PNG,
    [Description("Tagged Image Format")]
    TIFF,
    [Description("Windows Media Photo Format")]
    WDP
}

et un convertisseur de valeur ...

public class ImageFormatValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is ImageFormat format)
        {
            return GetString(format);
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is string s)
        {
            return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':')));
        }
        return null;
    }

    public string[] Strings => GetStrings();

    public static string GetString(ImageFormat format)
    {
        return format.ToString() + ": " + GetDescription(format);
    }

    public static string GetDescription(ImageFormat format)
    {
        return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute<DescriptionAttribute>().Description;

    }
    public static string[] GetStrings()
    {
        List<string> list = new List<string>();
        foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
        {
            list.Add(GetString(format));
        }

        return list.ToArray();
    }
}

Ressources...

    <local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>

Déclaration XAML ...

    <ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
              SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>

Voir le modèle ...

    private ImageFormat _imageFormat = ImageFormat.JPG;
    public ImageFormat Format
    {
        get => _imageFormat;
        set
        {
            if (_imageFormat != value)
            {
                _imageFormat = value;
                OnPropertyChanged();
            }
        }
    }

Combobox résultante ...

ComboBox lié à enum

AQuirky
la source
Pour moi, c'est la meilleure solution à la question: simple, facile à comprendre, simple à mettre en œuvre.
Informagic
Le problème avec cette solution est qu'elle n'est pas localisable.
Robin Davies le
@RobinDavies vous pouvez le localiser. Nécessite un DescriptionAttribute personnalisé dont j'ai construit quelques-uns. Voir cette question SO pour quelques idées: stackoverflow.com/questions/7398653/…
AQuirky
2
public class EnumItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!value.GetType().IsEnum)
            return false;

        var enumName = value.GetType();
        var obj = Enum.Parse(enumName, value.ToString());

        return System.Convert.ToInt32(obj);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Enum.ToObject(targetType, System.Convert.ToInt32(value));
    }
}

Vous devriez étendre la réponse de Rogers et Greg avec un tel type de convertisseur de valeur Enum, si vous liez directement aux propriétés du modèle objet enum.

Ruberoïde
la source
1

Si vous liez à une propriété enum réelle sur votre ViewModel, et non à une représentation int d'un enum, les choses se compliquent. J'ai trouvé qu'il était nécessaire de se lier à la représentation sous forme de chaîne, PAS à la valeur int comme prévu dans tous les exemples ci-dessus.

Vous pouvez savoir si c'est le cas en liant une simple zone de texte à la propriété à laquelle vous souhaitez vous lier sur votre ViewModel. S'il affiche du texte, liez à la chaîne. S'il affiche un nombre, liez à la valeur. Remarque J'ai utilisé Display deux fois, ce qui serait normalement une erreur, mais c'est la seule façon dont cela fonctionne.

<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
                      DisplayMemberPath="Display"
                      SelectedValuePath="Display"
                      ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />

Greg

Greg Gum
la source
Cette réponse semble incomplète: * Qu'est-ce que / core /?
trapicki
1

J'ai aimé la réponse de tom.maruska , mais je devais prendre en charge tout type d'énumération que mon modèle pourrait rencontrer au moment de l'exécution. Pour cela, j'ai dû utiliser une liaison pour spécifier le type de l'extension de balisage. J'ai pu travailler dans cette réponse de nicolay.anykienko pour proposer une extension de balisage très flexible qui fonctionnerait dans tous les cas auxquels je pense. Il se consomme comme ceci:

<ComboBox SelectedValue="{Binding MyEnumProperty}" 
          SelectedValuePath="Value"
          ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" 
          DisplayMemberPath="DisplayName" />

La source de l'extension de balisage écrasée référencée ci-dessus:

class EnumToObjectArray : MarkupExtension
{
    public BindingBase SourceEnum { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        DependencyObject targetObject;
        DependencyProperty targetProperty;

        if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
        {
            targetObject = (DependencyObject)target.TargetObject;
            targetProperty = (DependencyProperty)target.TargetProperty;
        }
        else
        {
            return this;
        }

        BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum);

        var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType();

        if (type.BaseType != typeof(System.Enum)) return this;

        return Enum.GetValues(type)
            .Cast<Enum>()
            .Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) });
    }

    private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum)
                       , typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

    /// <summary>
    /// Extension method which returns the string specified in the Description attribute, if any.  Oherwise, name is returned.
    /// </summary>
    /// <param name="value">The enum value.</param>
    /// <returns></returns>
    public static string Description(Enum value)
    {
        var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attrs.Any())
            return (attrs.First() as DescriptionAttribute).Description;

        //Fallback
        return value.ToString().Replace("_", " ");
    }
}
Hamish
la source
1

Explication simple et claire: http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

xmlns:local="clr-namespace:BindingEnums"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

...

<Window.Resources>
    <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:Status"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

...

<Grid>
    <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
              ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
jlo-gmail
la source
0

En utilisant ReactiveUI, j'ai créé la solution alternative suivante. Ce n'est pas une solution tout-en-un élégante, mais je pense qu'elle est à tout le moins lisible.

Dans mon cas, la liaison d'une liste de enumà un contrôle est un cas rare, je n'ai donc pas besoin de mettre à l'échelle la solution sur la base de code. Cependant, le code peut être plus générique en changeant EffectStyleLookup.Itemen un Object. Je l'ai testé avec mon code, aucune autre modification n'est nécessaire. Ce qui signifie que la seule classe d'assistance peut être appliquée à n'importe quelle enumliste. Bien que cela réduirait sa lisibilité -ReactiveList<EnumLookupHelper> n'a pas beaucoup de sens.

Utilisation de la classe d'assistance suivante:

public class EffectStyleLookup
{
    public EffectStyle Item { get; set; }
    public string Display { get; set; }
}

Dans le ViewModel, convertissez la liste des énumérations et exposez-la en tant que propriété:

public ViewModel : ReactiveObject
{
  private ReactiveList<EffectStyleLookup> _effectStyles;
  public ReactiveList<EffectStyleLookup> EffectStyles
  {
    get { return _effectStyles; }
    set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
  }

  // See below for more on this
  private EffectStyle _selectedEffectStyle;
  public EffectStyle SelectedEffectStyle
  {
    get { return _selectedEffectStyle; }
    set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
  }

  public ViewModel() 
  {
    // Convert a list of enums into a ReactiveList
    var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
      .Select( x => new EffectStyleLookup() { 
        Item = x, 
        Display = x.ToString()
      });

    EffectStyles = new ReactiveList<EffectStyle>( list );
  }
}

Dans le ComboBox, utilisez la SelectedValuePathpropriété pour vous lier à la enumvaleur d' origine :

<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

Dans la vue, cela nous permet de lier l'original enumà SelectedEffectStyledans le ViewModel, mais d'afficher la ToString()valeur dans le ComboBox:

this.WhenActivated( d =>
{
  d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
  d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});
Mitkins
la source
Je pense que votre ViewModel a une erreur. 1) Ne devrait-il pas s'agir d'une ReactiveList de EffectStyleLookup?, 2) Vous devez d'abord créer une ReactiveList <T> () vide. Ajoutez ensuite les éléments. Enfin: ReactiveList <T> est désormais obsolète (mais fonctionne toujours). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (liste); Merci d'avoir pris le temps de le montrer.
user1040323
0

J'ajoute mon commentaire (en VB, malheureusement, mais le concept peut être facilement reproduit en C # en un clin d'œil), car je devais simplement faire référence à cela et n'aimais aucune des réponses car elles étaient trop complexes. Cela ne devrait pas être aussi difficile.

Alors j'ai trouvé un moyen plus simple. Liez les énumérateurs à un dictionnaire. Liez ce dictionnaire au Combobox.

Ma combobox:

<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" 
    Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" 
    SelectedValuePath="Key" DisplayMemberPath="Value" />

Mon code derrière. Espérons que cela aide quelqu'un d'autre.

Dim tDict As New Dictionary(Of Integer, String)
Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType))
For Each x As Helper.Enumerators.AllowedType In types
    Dim z = x.ToString()
    Dim y = CInt(x)
    tDict.Add(y, z)
Next

cmbRole.ClearValue(ItemsControl.ItemsSourceProperty)
cmbRole.ItemsSource = tDict
Laki Politis
la source
La réponse de Kyrylo est beaucoup plus simple que la vôtre - je ne comprends pas ce qui est compliqué? Son ne nécessite aucune conversion dans le code.
Johnathon Sullinger
Je ne voulais pas placer toute ma logique entre les mains de XAML. Je préfère faire ma logique à ma façon (pas toujours la meilleure façon), mais cela me permet de comprendre où et pourquoi quelque chose ne se passe pas comme prévu. Le sien est moins compliqué, mais s'appuie sur XAML / WPF pour faire la logique. Je ne suis tout simplement pas fan de ça. 10000 façons d'écorcher un chat, vous savez?
Laki Politis
C'est suffisant. Personnellement, je préfère utiliser des fonctionnalités déjà construites, prêtes à l'emploi, pour moi mais c'est juste ma préférence;) A chacun il y en a!
Johnathon Sullinger
Oui monsieur! Je comprends parfaitement. J'ai été forcé dans le développement de logiciels provenant du développement Web. Je n'ai pas été aussi à jour sur WPF et j'ai dû apprendre beaucoup au fur et à mesure. Je ne comprends toujours pas toutes les subtilités des contrôles WPF / XAML, et j'ai donc trouvé plus de problèmes que de solutions dans la façon dont je m'attendrais à ce que les choses fonctionnent. Mais j'apprécie cette conversation. Cela m'a incité à faire plus de recherches.
Laki Politis
0

La solution de Nick peut être simplifiée davantage, sans rien d'extraordinaire, vous n'auriez besoin que d'un seul convertisseur:

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

Vous utilisez ensuite ceci partout où vous voulez que votre combo apparaisse:

<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />
Jack
la source
0

Je ne recommanderais pas de mettre en œuvre cela tel quel, mais j'espère que cela peut inspirer une bonne solution.

Disons que votre énumération est Foo. Ensuite, vous pouvez faire quelque chose comme ça.

public class FooViewModel : ViewModel
{
    private int _fooValue;

    public int FooValue
    {
        get => _fooValue;
        set
        {
            _fooValue = value;
            OnPropertyChange();
            OnPropertyChange(nameof(Foo));
            OnPropertyChange(nameof(FooName));
        }
    }
    public Foo Foo 
    { 
        get => (Foo)FooValue; 
        set 
        { 
            _fooValue = (int)value;
            OnPropertyChange();
            OnPropertyChange(nameof(FooValue));
            OnPropertyChange(nameof(FooName));
        } 
    }
    public string FooName { get => Enum.GetName(typeof(Foo), Foo); }

    public FooViewModel(Foo foo)
    {
        Foo = foo;
    }
}

Ensuite, sur la Window.Loadméthode, vous pouvez charger toutes les énumérations dans un ObservableCollection<FooViewModel>que vous pouvez définir comme DataContext de la zone de liste déroulante.

Shaamil Ahmed
la source
0

Je suis juste resté simple. J'ai créé une liste d'éléments avec les valeurs d'énumération dans mon ViewModel:

public enum InputsOutputsBoth
{
    Inputs,
    Outputs,
    Both
}

private IList<InputsOutputsBoth> _ioTypes = new List<InputsOutputsBoth>() 
{ 
    InputsOutputsBoth.Both, 
    InputsOutputsBoth.Inputs, 
    InputsOutputsBoth.Outputs 
};

public IEnumerable<InputsOutputsBoth> IoTypes
{
    get { return _ioTypes; }
    set { }
}

private InputsOutputsBoth _selectedIoType;

public InputsOutputsBoth SelectedIoType
{
    get { return _selectedIoType; }
    set
    {
        _selectedIoType = value;
        OnPropertyChanged("SelectedIoType");
        OnSelectionChanged();
    }
}

Dans mon code xaml, j'ai juste besoin de ceci:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Tsjakka
la source