Datenbindung einer Enum-Eigenschaft an eine ComboBox in WPF


256

Nehmen Sie als Beispiel den folgenden Code:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } }
}

Ich möchte, dass a die Eigenschaft ExampleProperty an eine ComboBox bindet, damit die Optionen "FooBar" und "BarFoo" angezeigt werden und im Modus TwoWay funktioniert. Optimalerweise soll meine ComboBox-Definition ungefähr so ​​aussehen:

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />

Derzeit sind in meinem Fenster Handler für die Ereignisse ComboBox.SelectionChanged und ExampleClass.PropertyChanged installiert, in denen ich die Bindung manuell durchführe.

Gibt es einen besseren oder einen kanonischen Weg? Würden Sie normalerweise Konverter verwenden und wie würden Sie die ComboBox mit den richtigen Werten füllen? Ich möchte jetzt noch nicht einmal mit i18n anfangen.

Bearbeiten

Daher wurde eine Frage beantwortet: Wie fülle ich die ComboBox mit den richtigen Werten?

Rufen Sie Enum-Werte als Liste von Zeichenfolgen über einen ObjectDataProvider von der statischen Enum.GetValues-Methode ab:

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

Dies kann ich als ItemsSource für meine ComboBox verwenden:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>

4
Ich erforschte diese und haben eine Lösung , dass Sie (komplett mit Lokalisierung) in WPF befindet verwenden hier .
ageektrapped

Antworten:


208

Sie können eine benutzerdefinierte Markup-Erweiterung erstellen.

Anwendungsbeispiel:

enum Status
{
    [Description("Available.")]
    Available,
    [Description("Not here right now.")]
    Away,
    [Description("I don't have time right now.")]
    Busy
}

An der Spitze Ihrer XAML:

    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

und dann...

<ComboBox 
    ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" 
    DisplayMemberPath="Description" 
    SelectedValue="{Binding CurrentStatus}"  
    SelectedValuePath="Value"  /> 

Und die Umsetzung ...

public class EnumerationExtension : MarkupExtension
  {
    private Type _enumType;


    public EnumerationExtension(Type enumType)
    {
      if (enumType == null)
        throw new ArgumentNullException("enumType");

      EnumType = enumType;
    }

    public Type EnumType
    {
      get { return _enumType; }
      private set
      {
        if (_enumType == value)
          return;

        var enumType = Nullable.GetUnderlyingType(value) ?? value;

        if (enumType.IsEnum == false)
          throw new ArgumentException("Type must be an Enum.");

        _enumType = value;
      }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      var enumValues = Enum.GetValues(EnumType);

      return (
        from object enumValue in enumValues
        select new EnumerationMember{
          Value = enumValue,
          Description = GetDescription(enumValue)
        }).ToArray();
    }

    private string GetDescription(object enumValue)
    {
      var descriptionAttribute = EnumType
        .GetField(enumValue.ToString())
        .GetCustomAttributes(typeof (DescriptionAttribute), false)
        .FirstOrDefault() as DescriptionAttribute;


      return descriptionAttribute != null
        ? descriptionAttribute.Description
        : enumValue.ToString();
    }

    public class EnumerationMember
    {
      public string Description { get; set; }
      public object Value { get; set; }
    }
  }

7
@ Gregor S. was mein: Aufzählung ist?
Joshua

14
@Crown 'my' ist ein Namespace-Präfix, das Sie oben in Ihrer xaml-Datei deklarieren: zB xmlns: my = "clr-namespace: namespace_to_enumeration_extension_class. Enumeration ist die Abkürzung für EnumerationExtension. In xaml müssen Sie nicht den gesamten Namen der Erweiterungsklasse schreiben .
Gregor Slavec

33
+1, aber die Menge an Code, die WPF benötigt, um die einfachsten Dinge zu erreichen, ist wirklich umwerfend
Konrad Morawski

1
Ich mag es nicht wirklich, wie Sie einen Verweis auf einen Teil Ihres Modells - den Aufzählungstyp - in der Ansicht, im ItemsSourceParameter verwenden. Um die Ansicht und das Modell entkoppelt zu halten, müsste ich eine Kopie der Aufzählung im ViewModel erstellen und ViewModel codieren, um zwischen den beiden zu übersetzen ... Was die Lösung nicht mehr so ​​einfach machen würde. Oder gibt es eine Möglichkeit, den Typ selbst aus ViewModel bereitzustellen?
Lampak

6
Eine weitere Einschränkung besteht darin, dass Sie dies nicht tun können, wenn Sie mehrere Sprachen haben.
River-Claire Williamson

176

Im Ansichtsmodell können Sie haben:

public MyEnumType SelectedMyEnumType 
{
    get { return _selectedMyEnumType; }
    set { 
            _selectedMyEnumType = value;
            OnPropertyChanged("SelectedMyEnumType");
        }
}

public IEnumerable<MyEnumType> MyEnumTypeValues
{
    get
    {
        return Enum.GetValues(typeof(MyEnumType))
            .Cast<MyEnumType>();
    }
}

In XAML ItemSourcebindet das an MyEnumTypeValuesund SelectedItembindet an SelectedMyEnumType.

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>

Dies hat in meiner Universal-App hervorragend funktioniert und war sehr einfach zu implementieren. Danke dir!
Nathan Strutz

96

Ich bevorzuge es, den Namen enum in der Benutzeroberfläche nicht zu verwenden. Ich bevorzuge es, einen anderen Wert für user ( DisplayMemberPath) und einen anderen Wert für value (in diesem Fall enum) ( SelectedValuePath) zu verwenden. Diese beiden Werte können gepackt werdenKeyValuePair in das Wörterbuch und dort gespeichert werden.

XAML

<ComboBox Name="fooBarComboBox" 
          ItemsSource="{Binding Path=ExampleEnumsWithCaptions}" 
          DisplayMemberPath="Value" 
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" > 

C #

public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
    new Dictionary<ExampleEnum, string>()
    {
        {ExampleEnum.FooBar, "Foo Bar"},
        {ExampleEnum.BarFoo, "Reversed Foo Bar"},
        //{ExampleEnum.None, "Hidden in UI"},
    };


private ExampleEnum example;
public ExampleEnum ExampleProperty
{
    get { return example; }
    set { /* set and notify */; }
}

BEARBEITEN: Kompatibel mit dem MVVM-Muster.


14
Ich denke, Ihre Antwort wird unterschätzt, es scheint die beste Option zu sein, wenn man bedenkt, was ComboBox selbst erwartet. Vielleicht könnten Sie mit einen Wörterbuch-Builder in den Getter einfügen Enum.GetValues, aber das würde den Teil der anzuzeigenden Namen nicht lösen. Am Ende und insbesondere wenn I18n implementiert ist, müssen Sie die Daten manuell ändern, wenn sich die Aufzählung ändert. Aber Enums sollten sich nicht oft, wenn überhaupt, ändern, oder? +1
Heltonbiker

2
Diese Antwort ist fantastisch UND ermöglicht das Lokalisieren der Aufzählungsbeschreibungen ... Danke dafür!
Shay

2
Diese Lösung ist sehr gut, da sie sowohl die Aufzählung als auch die Lokalisierung mit weniger Code als andere Lösungen handhabt!
Hfann

2
Das Problem mit Dictionary ist, dass die Schlüssel nach Hashwerten sortiert sind, sodass nur wenig Kontrolle darüber besteht. Obwohl etwas ausführlicher, habe ich stattdessen List <KeyValuePair <enum, string >> verwendet. Gute Idee.
Kevin Brock

3
@CoperNick @Pragmateek neue Korrektur:public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } = new Dictionary<ExampleEnum, string>() { {ExampleEnum.FooBar, "Foo Bar"}, {ExampleEnum.BarFoo, "Reversed Foo Bar"}, //{ExampleEnum.None, "Hidden in UI"}, };
Jinjinov

40

Ich weiß nicht, ob dies nur in XAML möglich ist, versuche aber Folgendes:

Geben Sie Ihrer ComboBox einen Namen, damit Sie im folgenden Code darauf zugreifen können: "typesComboBox1"

Versuchen Sie nun Folgendes

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));

24

Basierend auf der akzeptierten, aber jetzt gelöschten Antwort von ageektrapped ich eine abgespeckte Version ohne einige der erweiterten Funktionen erstellt. Der gesamte Code ist hier enthalten, damit Sie ihn kopieren und einfügen können und nicht durch Link-Rot blockiert werden.

Ich benutze das, System.ComponentModel.DescriptionAttributewas wirklich für Entwurfszeitbeschreibungen gedacht ist. Wenn Sie dieses Attribut nicht mögen, können Sie Ihr eigenes erstellen, aber ich denke, dass die Verwendung dieses Attributs wirklich die Arbeit erledigt. Wenn Sie das Attribut nicht verwenden, wird standardmäßig der Name des Aufzählungswerts im Code verwendet.

public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

Hier ist die Klasse, die als Elementquelle verwendet wird:

public class EnumItemsSource : Collection<String>, IValueConverter {

  Type type;

  IDictionary<Object, Object> valueToNameMap;

  IDictionary<Object, Object> nameToValueMap;

  public Type Type {
    get { return this.type; }
    set {
      if (!value.IsEnum)
        throw new ArgumentException("Type is not an enum.", "value");
      this.type = value;
      Initialize();
    }
  }

  public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.valueToNameMap[value];
  }

  public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.nameToValueMap[value];
  }

  void Initialize() {
    this.valueToNameMap = this.type
      .GetFields(BindingFlags.Static | BindingFlags.Public)
      .ToDictionary(fi => fi.GetValue(null), GetDescription);
    this.nameToValueMap = this.valueToNameMap
      .ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    Clear();
    foreach (String name in this.nameToValueMap.Keys)
      Add(name);
  }

  static Object GetDescription(FieldInfo fieldInfo) {
    var descriptionAttribute =
      (DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
    return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name;
  }

}

Sie können es in XAML wie folgt verwenden:

<Windows.Resources>
  <local:EnumItemsSource
    x:Key="ExampleEnumItemsSource"
    Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
  ItemsSource="{StaticResource ExampleEnumItemsSource}"
  SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/> 

23

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

Diese Lösung finden Sie in diesem Blog


Gute Antwort. Im Übrigen müssen Sie sich keine Gedanken über Converterdas Problem der Aufzählung von Zeichenfolgen machen.
DonBoitnott

1
Linked Solution scheint tot zu sein (koreanischer oder japanischer Text?). Wenn ich Ihren Code in meine XAML-Ressourcen einfüge, heißt es, dass Enum in einem WPF-Projekt nicht unterstützt wird.
Sebastian

6

Meine Lieblingsmethode ist die Verwendung von a, ValueConvertersodass sowohl ItemsSource als auch SelectedValue an dieselbe Eigenschaft gebunden sind. Dies erfordert keine zusätzlichen Eigenschaften , um Ihr ViewModel schön und sauber zu halten.

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

Und die Definition des Konverters:

public static class EnumHelper
{
  public static string Description(this Enum e)
  {
    return (e.GetType()
             .GetField(e.ToString())
             .GetCustomAttributes(typeof(DescriptionAttribute), false)
             .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString();
  }
}

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

Dieser Konverter funktioniert mit jeder Aufzählung. ValueDescriptionist nur eine einfache Klasse mit einer ValueEigenschaft und einer DescriptionEigenschaft. Sie können genauso gut ein Tuplewith Item1und Item2oder ein KeyValuePairwith Keyand Valueanstelle von Value and Description oder eine andere Klasse Ihrer Wahl verwenden, sofern diese einen Aufzählungswert und eine Zeichenfolgenbeschreibung dieses Aufzählungswerts enthalten kann.


Gute Antwort! Für die ValueDescriptionKlasse kann die DescriptionEigenschaft weggelassen werden, wenn sie nicht benötigt wird. Eine einfache Klasse mit nur ValueEigentum funktioniert auch!
Pogosama

Wenn Sie an einen RadioButton binden möchten, muss die Convert-Methode eine Liste von Zeichenfolgen zurückgeben, dh .Select(e => e.ToString())anstatt die ValueDescriptionKlasse zu verwenden.
Pogosama

Anstelle von könnte ValueDescriptionauch ein KeyValuePairverwendet werden, wie hier gezeigt
Apfelkuacha

5

Hier ist eine generische Lösung mit einer Hilfsmethode. Dies kann auch eine Aufzählung eines beliebigen zugrunde liegenden Typs (Byte, Sbyte, Uint, Long usw.) verarbeiten.

Hilfsmethode:

static IEnumerable<object> GetEnum<T>() {
    var type    = typeof(T);
    var names   = Enum.GetNames(type);
    var values  = Enum.GetValues(type);
    var pairs   =
        Enumerable.Range(0, names.Length)
        .Select(i => new {
                Name    = names.GetValue(i)
            ,   Value   = values.GetValue(i) })
        .OrderBy(pair => pair.Name);
    return pairs;
}//method

Modell anzeigen:

public IEnumerable<object> EnumSearchTypes {
    get {
        return GetEnum<SearchTypes>();
    }
}//property

Kombinationsfeld:

<ComboBox
    SelectedValue       ="{Binding SearchType}"
    ItemsSource         ="{Binding EnumSearchTypes}"
    DisplayMemberPath   ="Name"
    SelectedValuePath   ="Value"
/>

5

Sie können so etwas in Betracht ziehen:

  1. Definieren Sie einen Stil für den Textblock oder ein anderes Steuerelement, mit dem Sie Ihre Aufzählung anzeigen möchten:

    <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="Text" Value="&lt;NULL&gt;"/>
        <Style.Triggers>
            <Trigger Property="Tag">
                <Trigger.Value>
                    <proj:YourEnum>Value1<proj:YourEnum>
                </Trigger.Value>
                <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/>
            </Trigger>
            <!-- add more triggers here to reflect your enum -->
        </Style.Triggers>
    </Style>
  2. Definieren Sie Ihren Stil für ComboBoxItem

    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  3. Fügen Sie eine Combobox hinzu und laden Sie sie mit Ihren Enum-Werten:

    <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
        <ComboBox.Items>
            <ComboBoxItem>
                <proj:YourEnum>Value1</proj:YourEnum>
            </ComboBoxItem>
        </ComboBox.Items>
    </ComboBox>

Wenn Ihre Aufzählung groß ist, können Sie dies natürlich auch im Code tun, ohne viel zu tippen. Ich mag diesen Ansatz, da er die Lokalisierung vereinfacht - Sie definieren alle Vorlagen einmal und aktualisieren dann nur Ihre String-Ressourcendateien.


Der SelectedValuePath = "Content" hat mir hier geholfen. Ich habe meine ComboBoxItems als Zeichenfolgenwerte und kann ComboBoxItem immer wieder nicht in meinen Aufzählungstyp konvertieren. Danke
adriaanp

2

Wenn Sie eine MVVM verwenden, die auf der Antwort von @rudigrobler basiert, können Sie Folgendes tun:

Fügen Sie der ViewModel- Klasse die folgende Eigenschaft hinzu

public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum));

Führen Sie dann in der XAML Folgendes aus:

<ComboBox ItemsSource="{Binding ExampleEnumValues}" ... />

1

Dies ist eine DevExpressspezifische Antwort, die auf der am besten bewerteten Antwort von basiert Gregor S.(derzeit hat sie 128 Stimmen).

Dies bedeutet, dass wir das Styling über die gesamte Anwendung hinweg konsistent halten können:

Geben Sie hier die Bildbeschreibung ein

Leider funktioniert die ursprüngliche Antwort mit einem ComboBoxEditvon DevExpress ohne einige Änderungen nicht.

Zunächst die XAML für ComboBoxEdit:

<dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}"
    SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    DisplayMember="Description"
    MinWidth="144" Margin="5" 
    HorizontalAlignment="Left"
    IsTextEditable="False"
    ValidateOnTextInput="False"
    AutoComplete="False"
    IncrementalFiltering="True"
    FilterCondition="Like"
    ImmediatePopup="True"/>

Unnötig zu sagen, müssen Sie zeigen xamlExtensions auf den Namespace zeigen, der die XAML-Erweiterungsklasse enthält (die unten definiert ist):

xmlns:xamlExtensions="clr-namespace:XamlExtensions"

Und wir müssen zeigen myEnum auf den Namespace zeigen, der die Aufzählung enthält:

xmlns:myEnum="clr-namespace:MyNamespace"

Dann die Aufzählung:

namespace MyNamespace
{
    public enum EnumFilter
    {
        [Description("Free as a bird")]
        Free = 0,

        [Description("I'm Somewhat Busy")]
        SomewhatBusy = 1,

        [Description("I'm Really Busy")]
        ReallyBusy = 2
    }
}

Das Problem mit der XAML ist, dass wir sie nicht verwenden können SelectedItemValue, da dies einen Fehler auslöst, da auf den Setter nicht zugegriffen werden kann (ein kleines Versehen von Ihrer Seite DevExpress). Also müssen wir unsere ändernViewModel , um den Wert direkt vom Objekt zu erhalten:

private EnumFilter _filterSelected = EnumFilter.All;
public object FilterSelected
{
    get
    {
        return (EnumFilter)_filterSelected;
    }
    set
    {
        var x = (XamlExtensionEnumDropdown.EnumerationMember)value;
        if (x != null)
        {
            _filterSelected = (EnumFilter)x.Value;
        }
        OnPropertyChanged("FilterSelected");
    }
}

Der Vollständigkeit halber hier die XAML-Erweiterung aus der ursprünglichen Antwort (leicht umbenannt):

namespace XamlExtensions
{
    /// <summary>
    ///     Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the
    ///     dropdown box by using the [Description] attribute on the enum values.
    /// </summary>
    public class XamlExtensionEnumDropdown : MarkupExtension
    {
        private Type _enumType;


        public XamlExtensionEnumDropdown(Type enumType)
        {
            if (enumType == null)
            {
                throw new ArgumentNullException("enumType");
            }

            EnumType = enumType;
        }

        public Type EnumType
        {
            get { return _enumType; }
            private set
            {
                if (_enumType == value)
                {
                    return;
                }

                var enumType = Nullable.GetUnderlyingType(value) ?? value;

                if (enumType.IsEnum == false)
                {
                    throw new ArgumentException("Type must be an Enum.");
                }

                _enumType = value;
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var enumValues = Enum.GetValues(EnumType);

            return (
                from object enumValue in enumValues
                select new EnumerationMember
                       {
                           Value = enumValue,
                           Description = GetDescription(enumValue)
                       }).ToArray();
        }

        private string GetDescription(object enumValue)
        {
            var descriptionAttribute = EnumType
                .GetField(enumValue.ToString())
                .GetCustomAttributes(typeof (DescriptionAttribute), false)
                .FirstOrDefault() as DescriptionAttribute;


            return descriptionAttribute != null
                ? descriptionAttribute.Description
                : enumValue.ToString();
        }

        #region Nested type: EnumerationMember
        public class EnumerationMember
        {
            public string Description { get; set; }
            public object Value { get; set; }
        }
        #endregion
    }
}

Haftungsausschluss: Ich bin nicht mit DevExpress verbunden. Telerik ist auch eine großartige Bibliothek.


Ich bin nicht mit DevExpress verbunden. Telerik hat auch sehr gute Bibliotheken, und diese Technik ist möglicherweise nicht einmal für ihre Bibliothek erforderlich.
Contango

0

Versuchen Sie es mit

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
    SelectedValue="{Binding Path=ExampleProperty}" />

Das funktioniert nicht. Die Combobox zeigt nur einen leeren Text an und das Ändern ändert nichts. Ich denke, hier einen Konverter einzusetzen, wäre die beste Lösung.
Maximilian

0

Ich habe ein Open Source CodePlex- Projekt erstellt, das dies tut. Sie können das NuGet-Paket hier herunterladen .

<enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" />
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.