Wie binde ich eine Enumeration an ein Combobox-Steuerelement in WPF?

182

Ich versuche ein einfaches Beispiel zu finden, in dem die Aufzählungen so angezeigt werden, wie sie sind. Alle Beispiele, die ich gesehen habe, versuchen, gut aussehende Anzeigezeichenfolgen hinzuzufügen, aber ich möchte diese Komplexität nicht.

Grundsätzlich habe ich eine Klasse, die alle Eigenschaften enthält, die ich binde, indem ich zuerst den DataContext auf diese Klasse setze und dann die Bindung wie folgt in der xaml-Datei spezifiziere:

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

Dies zeigt jedoch nicht die Aufzählungswerte in den ComboBoxas-Elementen an.

Joan Venge
quelle
9
Folgendes suchen Sie: WPF ObjectDataProvider - Bindungsaufzählung an ComboBox Sie können auch das vollständige Quellcodebeispiel von dort herunterladen.
Die beste Antwort meiner Meinung nach ist in: stackoverflow.com/questions/58743/…
Gimpy

Antworten:

307

Sie können dies aus Code heraus tun, indem Sie beispielsweise den folgenden Code in den Window- LoadedEreignishandler einfügen :

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

Wenn Sie es in XAML binden müssen, müssen Sie es verwenden ObjectDataProvider, um ein Objekt zu erstellen, das als Bindungsquelle verfügbar ist:

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

Machen Sie auf den nächsten Code aufmerksam:

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

Anleitung zum Zuordnen von Namespace und Assembly, die Sie auf MSDN lesen können .

Kyrylo M.
quelle
1
Getestetes Beispiel vom ersten Link, funktioniert OK. Siehe hinzugefügten Code und Kommentar in meiner Antwort.
Kyrylo M
1
Sie haben Ihr Problem in MSDN-Foren gefunden ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Versuchen Sie, das Projekt zu bereinigen und neu zu erstellen. Wahrscheinlich sollten Sie dieses Problem hier bei einer anderen Frage stellen. Dies ist das einzige, was ich raten kann ... Wie auch immer, das gezeigte Beispiel ist korrekt.
Kyrylo M
1
Danke, das ist bizarr, aber ich habe ähnliche Sachen mit wpf-Wahnsinn gesehen. Ich werde es tun und dich wissen lassen. Übrigens ist dies das gleiche Problem, das hier beschrieben wird: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge
2
Sie müssen einen Verweis darauf hinzufügen xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"und XAML hinzufügen , um ihn zu verwenden. Hier ist Leitfaden: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M
4
Sie können solche Tools wie ReSharper verwenden. Es analysiert alle referenzierten Assemblys und gibt Vorschläge, was eingeschlossen werden muss. Sie müssen nicht schreiben - wählen Sie einfach aus den Optionen.
Kyrylo M
115

Ich mag es, wenn alle Objekte, die ich binde, in meinem definiert werden ViewModel, deshalb versuche ich, die Verwendung zu vermeiden<ObjectDataProvider> binde, in der xaml nach Möglichkeit .

Meine Lösung verwendet keine in der Ansicht definierten Daten und keinen Code-Behind. Nur eine DataBinding, ein wiederverwendbarer ValueConverter, eine Methode zum Abrufen einer Sammlung von Beschreibungen für einen beliebigen Enum-Typ und eine einzelne Eigenschaft im ViewModel, an die gebunden werden soll.

Wenn ich einen Enuman ComboBoxden Text binden möchte, den ich anzeigen möchte, stimmt er nie mit den Werten von überein. Daher verwende Enumich das [Description()]Attribut, um ihm den Text zu geben, den ich tatsächlich im Text sehen möchte ComboBox. Wenn ich eine Anzahl von Wochentagen hätte, würde es ungefähr so ​​aussehen:

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

Zuerst habe ich eine Hilfsklasse mit ein paar Methoden erstellt, um mit Aufzählungen umzugehen. Eine Methode erhält eine Beschreibung für einen bestimmten Wert, die andere Methode erhält alle Werte und ihre Beschreibungen für einen Typ.

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

Als nächstes erstellen wir eine ValueConverter. Das Erben von MarkupExtensionerleichtert die Verwendung in XAML, sodass wir es nicht als Ressource deklarieren müssen.

[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;
  }
}

Ich ViewModelbenötige nur 1 Eigenschaft, an die ich Viewsowohl für die SelectedValueals auch ItemsSourcefür die Combobox binden kann :

private DayOfWeek dayOfWeek;

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

Und zum Schluss die ComboBoxAnsicht binden (mit der ValueConverterin der ItemsSourceBindung) ...

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

Um diese Lösung zu implementieren, müssen Sie nur meine EnumHelperKlasse und EnumToCollectionConverterKlasse kopieren . Sie werden mit allen Aufzählungen arbeiten. Ich habe es hier auch nicht aufgenommen, aber die ValueDescriptionKlasse ist nur eine einfache Klasse mit 2 öffentlichen Objekteigenschaften, eine aufgerufene Value, eine aufgerufene Description. Sie können das selbst erstellen oder den Code ändern, um ein Tuple<object, object>oder zu verwendenKeyValuePair<object, object>

Nick
quelle
9
Damit dies funktioniert, musste ich eine ValueDescriptionKlasse erstellen , die öffentliche Eigenschaften für ValueundDescription
Perchik
4
Ja, Sie können diesen Code auch ändern, um ein Tuple<T1, T2>oder oder KeyValuePair<TKey, TValue>anstelle der ValueDescriptionKlasse zu verwenden, und dann müssten Sie keinen eigenen erstellen.
Nick
Ich musste OnPropertyChanged (oder das Äquivalent) für beide ViewModel-Eigenschaften implementieren, nicht nur für SelectedClass.
Will
Sie sollten OnPropertyChanged nicht für die Eigenschaft implementieren müssen, die die Liste zurückgibt. Die Liste wird aus den Werten in einer Aufzählung generiert. Es wird sich zur Laufzeit nie ändern, und wenn es sich nie ändert, muss es niemanden benachrichtigen, dass es sich geändert hat. Außerdem wird mit der aktualisierten Version die List-Eigenschaft überhaupt nicht benötigt.
Nick
Wie ist ItemSource und SelectedValue der Combobox dieselbe Eigenschaft? Muss die ItemsSource nicht eine Liste sein? Oh, ich verstehe, weil der EnumHelper eine Liste von Objekten erstellt. Dies macht mein ViewModel tatsächlich einfacher, da ich keine separate Liste von Objekten verwalten muss, um die ItemSource zu füllen.
Stealth Rabbi
46

Ich habe eine andere Lösung mit MarkupExtension verwendet.

  1. Ich habe eine Klasse erstellt, die die Quelle der Elemente bereitstellt:

    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. Das ist fast alles ... Jetzt benutze es in XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Ändern Sie 'enums: States' in Ihre Aufzählung

tom.maruska
quelle
1
@Nick: Die akzeptierte Antwort bezieht sich auch auf die Aufzählung (oder das Modell, wie Sie sagten) in xaml. Ihre Lösung besteht darin, zwei Eigenschaften und ein Hintergrundfeld im Ansichtsmodell zu erstellen, was mir nicht gefallen hat (DRY-Regel). Und natürlich müssen Sie nicht e.ToString()für den Anzeigenamen verwenden. Sie können Ihren eigenen Übersetzer, Parser für Beschreibungsattribute, verwenden.
Tom.maruska
2
@ tom.maruska Ich versuche nicht, auf meine Antwort im Vergleich zu Ihrer Antwort einzugehen, aber seit Sie sie angesprochen haben, verstößt das Vorhandensein von 2 Eigenschaften nicht gegen die DRY-Regel, wenn es sich um 2 verschiedene Eigenschaften handelt, die unterschiedlichen Zwecken dienen. Und Ihre Antwort würde auch das Hinzufügen einer Eigenschaft erfordern (Sie haben diese Eigenschaft sogar selbst aufgerufen {Binding Path=WhereEverYouWant}), und wenn Sie möchten, dass sie die bidirektionale Bindung unterstützt, haben Sie auch ein Hintergrundfeld dafür. Sie ersetzen also nicht 2 Eigenschaften und 1 Hintergrundfeld, sondern nur 1 einzeilige schreibgeschützte Eigenschaft.
Nick
@ Nick Ja, Sie haben Recht mit dieser Eigenschaft und Hintergrundfeld :)
tom.maruska
24

Verwenden Sie ObjectDataProvider:

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

und dann an statische Ressource binden:

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

basierend auf diesem Artikel

druss
quelle
4
Perfekt einfache Lösung. Namespace für System wie in Kirmirs Antwort:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite
Funktioniert gut in WPF-Projekten von Visual Studio 2017.
Sorush
10

Nicks Antwort hat mir wirklich geholfen, aber ich erkannte, dass sie leicht angepasst werden konnte, um eine zusätzliche Klasse, ValueDescription, zu vermeiden. Ich erinnerte mich, dass bereits eine KeyValuePair-Klasse im Framework vorhanden ist, sodass diese stattdessen verwendet werden kann.

Der Code ändert sich nur geringfügig:

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

und schließlich die XAML:

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

Ich hoffe das ist hilfreich für andere.

Roger
quelle
Meine erste Implementierung verwendete a, KeyValuePairaber am Ende entschied ich mich, a KeyValuePairzu verwenden, um etwas darzustellen, das kein Schlüssel-Wert-Paar ist, nur um zu vermeiden, dass eine trivial einfache Klasse geschrieben wird, was nicht viel Sinn machte. Die ValueDescriptionKlasse besteht nur aus 5 Zeilen, und 2 davon sind nur {und}
Nick
8

Sie müssen ein Array der Werte in der Aufzählung erstellen, das erstellt werden kann, indem Sie System.Enum.GetValues ​​() aufrufen und das übergebenType die Aufzählung übergeben wird, deren Elemente Sie möchten.

Wenn Sie dies für die ItemsSourceEigenschaft angeben , sollte sie mit allen Werten der Aufzählung gefüllt sein. Sie wollen wahrscheinlich binden SelectedIteman EffectStyle(vorausgesetzt , es ist eine Eigenschaft des gleichen Enum ist, und enthält den aktuellen Wert).

Andy
quelle
Danke, können Sie bitte den ersten Teil im Code zeigen? Ich bin nicht sicher, wo die Aufzählungswerte als Array gespeichert werden sollen. Die Eigenschaft enum befindet sich in einer anderen Klasse. Kann ich diesen GetValues-Schritt in xaml ausführen?
Joan Venge
4

Alle oben genannten Beiträge haben einen einfachen Trick verpasst. Anhand der Bindung von SelectedValue können Sie herausfinden, wie die ItemsSource AUTOMAGISCH gefüllt wird, sodass Ihr XAML-Markup gerecht ist.

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

Zum Beispiel in meinem ViewModel habe ich

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged ist mein INPC-Hook. Ihre können abweichen.

Die Implementierung von EnumComboBox ist wie folgt, aber zuerst brauche ich einen kleinen Helfer, um meine Aufzählungszeichenfolgen und -werte abzurufen

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

und die Hauptklasse (Hinweis: Ich verwende ReactiveUI, um Eigenschaftsänderungen über WhenAny zu verknüpfen.)

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";

        }
    }
}

Sie müssen den Stil auch in Generic.XAML richtig einstellen, sonst rendert Ihre Box nichts und Sie ziehen sich die Haare aus.

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

und das ist das Dies könnte natürlich erweitert werden, um i18n zu unterstützen, würde aber den Beitrag verlängern.

Bradgonesurfing
quelle
3

Universelle Apps scheinen etwas anders zu funktionieren. Es verfügt nicht über die volle Leistung von XAML mit vollem Funktionsumfang. Was für mich funktioniert hat ist:

  1. Ich habe eine Liste der Aufzählungswerte als Aufzählungen erstellt (nicht in Zeichenfolgen oder Ganzzahlen konvertiert) und die ComboBox ItemsSource daran gebunden
  2. Dann könnte ich die ComboBox ItemSelected an meine öffentliche Eigenschaft binden, deren Typ die betreffende Aufzählung ist

Nur zum Spaß habe ich eine kleine Klasse mit Vorlagen zusammengestellt, um dies zu unterstützen, und sie auf den MSDN-Beispielseiten veröffentlicht . Mit den zusätzlichen Bits kann ich optional die Namen der Aufzählungen überschreiben und einige der Aufzählungen ausblenden. Mein Code sieht schrecklich aus wie der von Nick (oben), den ich gerne früher gesehen hätte.

Ausführen des Beispiels;  Es enthält mehrere Zwei-Wege-Bindungen an die Aufzählung

PESMITH_MSFT
quelle
3

Es gibt viele ausgezeichnete Antworten auf diese Frage und ich reiche meine demütig ein. Ich finde, dass meine etwas einfacher und eleganter ist. Es ist nur ein Wertekonverter erforderlich.

Angesichts einer Aufzählung ...

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
}

und ein Wertekonverter ...

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

Ressourcen...

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

XAML-Deklaration ...

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

Modell anzeigen ...

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

Resultierende Combobox ...

ComboBox an Aufzählung gebunden

AQuirky
quelle
Für mich ist dies die beste Lösung für die Frage: einfach, leicht zu verstehen, einfach zu implementieren.
Informagic
Das Problem bei dieser Lösung ist, dass sie nicht lokalisierbar ist.
Robin Davies
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));
    }
}

Sie sollten die Antwort von Rogers und Greg mit einem solchen Enum-Wertkonverter erweitern, wenn Sie direkt an die Eigenschaften des Enum-Objektmodells binden.

Ruberoid
quelle
1

Wenn Sie an eine tatsächliche Enum-Eigenschaft in Ihrem ViewModel binden und nicht an eine int-Darstellung einer Enum, wird es schwierig. Ich fand es notwendig, an die Zeichenfolgendarstellung zu binden, NICHT an den int-Wert, wie in allen obigen Beispielen erwartet.

Sie können feststellen, ob dies der Fall ist, indem Sie ein einfaches Textfeld an die Eigenschaft binden, an die Sie in Ihrem ViewModel binden möchten. Wenn Text angezeigt wird, binden Sie ihn an die Zeichenfolge. Wenn eine Zahl angezeigt wird, binden Sie an den Wert. Hinweis Ich habe Display zweimal verwendet, was normalerweise ein Fehler wäre, aber es ist die einzige Möglichkeit, wie es funktioniert.

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

Greg

Greg Gum
quelle
Diese Antwort scheint unvollständig: * Was ist / core /?
Trapicki
1

Ich mochte die Antwort von tom.maruska , aber ich musste jeden Aufzählungstyp unterstützen, auf den meine Vorlage zur Laufzeit stoßen könnte. Dafür musste ich eine Bindung verwenden, um den Typ für die Markup-Erweiterung anzugeben. Ich konnte in dieser Antwort von nicolay.anykienko eine sehr flexible Markup-Erweiterung entwickeln, die auf jeden Fall funktionieren würde, was mir einfällt. Es wird so konsumiert:

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

Die Quelle für die oben genannte Mashed-Up-Markup-Erweiterung:

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
quelle
1

Einfache und klare Erklärung: 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
quelle
0

Mit ReactiveUIhabe ich die folgende alternative Lösung erstellt. Es ist keine elegante All-in-One-Lösung, aber ich denke zumindest, dass sie lesbar ist.

In meinem Fall ist das Binden einer Liste enuman ein Steuerelement ein seltener Fall, sodass ich die Lösung nicht über die Codebasis skalieren muss. Der Code kann jedoch durch Ändern EffectStyleLookup.Itemin einen Code allgemeiner gestaltet werden Object. Ich habe es mit meinem Code getestet, es sind keine weiteren Änderungen erforderlich. Dies bedeutet, dass die eine Hilfsklasse auf jede enumListe angewendet werden kann . Das würde zwar die Lesbarkeit beeinträchtigen - ReactiveList<EnumLookupHelper>hat aber keinen großen Klang.

Verwenden der folgenden Hilfsklasse:

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

Konvertieren Sie im ViewModel die Liste der Aufzählungen und machen Sie sie als Eigenschaft verfügbar:

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

In der ComboBox, nutzen die SelectedValuePathEigenschaft, auf dem Original zu binden enumWert:

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

In der Ansicht können wir das Original enuman das SelectedEffectStyleim ViewModel binden , aber den ToString()Wert in folgendem anzeigen 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
quelle
Ich denke, Ihr ViewModel hat einen Fehler. 1) Sollte es nicht eine ReactiveList von EffectStyleLookup sein?, 2) Sie sollten zuerst eine leere ReactiveList <T> () erstellen. Fügen Sie dann die Elemente hinzu. Endlich: ReactiveList <T> ist jetzt veraltet (funktioniert aber immer noch). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (Liste); Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu zeigen.
user1040323
0

Ich füge meinen Kommentar hinzu (in VB leider, aber das Konzept kann sofort auf C # repliziert werden), weil ich nur darauf verweisen musste und keine der Antworten mochte, da sie zu komplex waren. Es sollte nicht so schwierig sein.

Also habe ich mir einen einfacheren Weg ausgedacht. Binden Sie die Enumeratoren an ein Wörterbuch. Binden Sie das Wörterbuch an die Combobox.

Meine Combobox:

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

Mein Code-Behind. Hoffentlich hilft das jemand anderem.

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
quelle
Kyrylos Antwort ist viel einfacher als deine - ich verstehe nicht, was daran kompliziert ist? Sein erfordert keine Konvertierung in Code.
Johnathon Sullinger
Ich wollte nicht meine ganze Logik in die Hände von XAML legen. Ich ziehe es vor, meine Logik auf meine Weise zu machen (nicht immer die beste), aber es ermöglicht mir zu verstehen, wo und warum etwas nicht nach Plan läuft. Sein ist weniger kompliziert, aber er verlässt sich auf XAML / WPF, um die Logik zu machen. Ich bin einfach kein Fan davon. 10.000 Möglichkeiten, eine Katze zu häuten, weißt du?
Laki Politis
Meinetwegen. Ich persönlich bevorzuge es, bereits gebrauchsfertige Funktionen für mich zu verwenden, aber das ist nur meine Präferenz;) Für jeden gibt es eigene!
Johnathon Sullinger
Jawohl! Ich verstehe vollkommen. Ich bin aufgrund der Webentwicklung zur Softwareentwicklung gezwungen worden. Ich war bei WPF nicht so auf dem neuesten Stand und musste im Laufe der Zeit viel lernen. Ich verstehe immer noch nicht alle Feinheiten der WPF / XAML-Steuerelemente und habe daher mehr Probleme als Lösungen gefunden, wie ich erwarten würde, dass die Dinge funktionieren. Aber ich schätze dieses Gespräch. Es hat mich dazu gebracht, noch mehr Nachforschungen anzustellen.
Laki Politis
0

Nicks Lösung kann noch weiter vereinfacht werden, ohne dass Sie etwas Besonderes benötigen. Sie würden nur einen einzigen Konverter benötigen:

[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;
    }
}

Sie verwenden dies dann überall dort, wo Ihr Kombinationsfeld angezeigt werden soll:

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

Ich würde nicht empfehlen, dies so zu implementieren, wie es ist, aber hoffentlich kann dies eine gute Lösung inspirieren.

Angenommen, Ihre Aufzählung lautet Foo. Dann können Sie so etwas tun.

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

Anschließend können Sie bei der Window.LoadMethode alle Aufzählungen in eine laden, ObservableCollection<FooViewModel>die Sie als DataContext der Combobox festlegen können.

Shaamil Ahmed
quelle
0

Ich habe es einfach gehalten. Ich habe eine Liste von Elementen mit den Aufzählungswerten in meinem ViewModel erstellt:

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

In meinem XAML-Code brauche ich nur Folgendes:

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