Jak powiązać wyliczenie z kontrolką combobox w WPF?


182

Próbuję znaleźć prosty przykład, w którym wyliczenia są pokazane w obecnej postaci. Wszystkie przykłady, które widziałem, próbują dodać ładnie wyglądające ciągi wyświetlania, ale nie chcę takiej złożoności.

Zasadniczo mam klasę, która przechowuje wszystkie właściwości, które łączę, najpierw ustawiając DataContext na tę klasę, a następnie określając takie powiązanie w pliku xaml:

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

Ale to nie pokazuje wartości wyliczenia w ComboBoxelementach as.


9
Oto, czego szukasz: WPF ObjectDataProvider - Wiązanie Enum do ComboBox Możesz również pobrać pełny przykład kodu źródłowego.

Moim zdaniem najlepsza odpowiedź to: stackoverflow.com/questions/58743/…
gimpy

Odpowiedzi:


306

Możesz to zrobić z kodu, umieszczając następujący kod w module Loadedobsługi zdarzeń Windows , na przykład:

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

Jeśli chcesz go powiązać w XAML, musisz użyć, ObjectDataProvideraby utworzyć obiekt dostępny jako źródło wiązania:

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

Zwróć uwagę na następny kod:

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

Przewodnik po mapowaniu przestrzeni nazw i zestawu, którą można czytać w MSDN .


1
Testowany przykład z pierwszego linku działa poprawnie. Zobacz dodany kod i komentarz w mojej odpowiedzi.
Kyrylo M

1
Znaleziono problem na forach MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Spróbuj wyczyścić i odbudować projekt. Prawdopodobnie powinieneś zapytać o ten problem tutaj w innym pytaniu. To jedyne, co mogę poradzić ... W każdym razie pokazany przykład jest poprawny.
Kyrylo M

1
Dzięki, to dziwne, ale widziałem podobne rzeczy z wpf szaleństwem. Zrobię to i dam ci znać. Btw to ten sam problem opisany tutaj: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge

2
Musisz dodać odniesienie do niego i dodać xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"XAML, aby go użyć. Oto przewodnik: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M

4
Możesz użyć takich narzędzi, jak ReSharper. Analizuje wszystkie przywoływane zespoły i podaje sugestie, co należy uwzględnić. Nie musisz pisać - po prostu wybierz jedną z opcji.
Kyrylo M

117

Podoba mi się, że wszystkie obiekty, które wiążę, są zdefiniowane w moim ViewModel, więc staram się unikać używania <ObjectDataProvider>w xaml, jeśli to możliwe.

Moje rozwiązanie nie wykorzystuje danych zdefiniowanych w widoku i nie zawiera kodu. Tylko DataBinding, ValueConverter wielokrotnego użytku, metoda uzyskiwania kolekcji opisów dla dowolnego typu Enum oraz pojedyncza właściwość w ViewModel do powiązania.

Gdy chcę związać Enumz ComboBoxtekstem chcę wyświetlić nigdy odpowiada wartości Enum, więc używam [Description()]atrybut dać to tekst, który faktycznie chcę widzieć w ComboBox. Gdybym miał wyliczenie dni tygodnia, wyglądałoby to tak:

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

Najpierw stworzyłem klasę pomocnika z kilkoma metodami radzenia sobie z wyliczeniami. Jedna metoda otrzymuje opis konkretnej wartości, druga metoda pobiera wszystkie wartości i ich opisy dla typu.

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

Następnie tworzymy ValueConverter. Dziedziczenie po MarkupExtensionułatwia korzystanie z XAML, więc nie musimy deklarować go jako zasobu.

[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 ViewModeltylko potrzebuje 1 właściwość, że mój Viewmoże wiązać się zarówno dla SelectedValuei ItemsSourceod combobox:

private DayOfWeek dayOfWeek;

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

I wreszcie, aby związać ComboBoxwidok (używając ValueConverterw ItemsSourcewiążący) ...

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

Aby wdrożyć to rozwiązanie, wystarczy skopiować moją EnumHelperklasę i EnumToCollectionConverterklasę. Będą pracować z dowolnymi wyliczeniami. Ponadto nie uwzględniłem go tutaj, ale ValueDescriptionklasa jest tylko prostą klasą z 2 właściwościami obiektów publicznych, jedną o nazwie Value, jedną o nazwie Description. Możesz to zrobić samodzielnie lub zmienić kod, aby użyć Tuple<object, object>lubKeyValuePair<object, object>


9
Aby to zadziałało, musiałem stworzyć ValueDescriptionklasę, która ma właściwości publiczne ValueiDescription
Perchik

4
Tak, możesz również zmienić ten kod, aby użyć klasy Tuple<T1, T2>lub lub KeyValuePair<TKey, TValue>zamiast, ValueDescriptioni wtedy nie musisz tworzyć własnego.
Nick

Musiałem zaimplementować OnPropertyChanged (lub odpowiednik) dla obu właściwości ViewModel, nie tylko SelectedClass.
Czy

Nie trzeba implementować OnPropertyChanged dla właściwości, która zwraca listę. Lista jest generowana na podstawie wartości w Enum. Nigdy się nie zmieni w czasie wykonywania, a gdy nigdy się nie zmienia, nigdy nie musi powiadamiać nikogo o zmianie. Ponadto w zaktualizowanej wersji właściwość listy nie jest nawet wcale potrzebna.
Nick

W jaki sposób ItemSource i SelectedValue combobox są tą samą właściwością. Czy pozycja ItemsSource nie musi być listą? Och, rozumiem, to dlatego, że EnumHelper tworzy listę obiektów. to faktycznie upraszcza mój ViewModel, ponieważ nie muszę utrzymywać osobnej listy obiektów, aby zapełnić element ItemSource.
Stealth Rabbi

46

Użyłem innego rozwiązania za pomocą MarkupExtension.

  1. Zrobiłem klasę, która zapewnia źródło przedmiotów:

    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. To prawie wszystko ... Teraz użyj go w XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Zmień „wyliczenia: Stany” na wyliczenie


1
@Nick: Zaakceptowana odpowiedź odnosi się również do enum (lub modelu, jak powiedziałeś) w Xaml. Twoje rozwiązanie tworzy 2 właściwości i pole zaplecza w modelu widoku, co mi się nie podobało (reguła DRY). I oczywiście nie musisz używać e.ToString()nazwy wyświetlanej. Możesz użyć własnego tłumacza, parsera atrybutów opisu, cokolwiek.
tom.maruska

2
@ tom.maruska Nie próbuję wnikać w moją odpowiedź w porównaniu z odpowiedzią, ale skoro o tym wspomniałeś, posiadanie 2 właściwości nie narusza zasady DRY, gdy są to 2 różne właściwości, które służą różnym celom. Twoja odpowiedź również wymagałaby dodania właściwości (nawet sam ją wywołałeś {Binding Path=WhereEverYouWant}), a jeśli chcesz, aby wspierała wiązanie dwukierunkowe, będziesz mieć także pole do tworzenia kopii zapasowych. Robiąc to, nie zastępujesz 2 właściwości i 1 pola bazowego, zastępujesz tylko 1 jednokreskową właściwość tylko do odczytu.
Nick

@Nick Tak, masz rację co do tej nieruchomości i zaplecza :)
tom.maruska

24

Użyj ObjectDataProvider:

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

a następnie powiąż z zasobem statycznym:

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

na podstawie tego artykułu


4
Idealnie proste rozwiązanie. Przestrzeń nazw dla Systemu jak w odpowiedzi Kirmira:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite

Działa dobrze w projektach WPF Visual Studio 2017.
Sorush

10

Odpowiedź Nicka naprawdę mi pomogła, ale zdałem sobie sprawę, że można ją nieco ulepszyć, aby uniknąć dodatkowej klasy, ValueDescription. Przypomniałem sobie, że istnieje już klasa KeyValuePair w ramach, więc można jej użyć zamiast tego.

Kod zmienia się tylko nieznacznie:

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

i wreszcie XAML:

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

Mam nadzieję, że jest to pomocne dla innych.


W mojej pierwszej implementacji wykorzystałem, KeyValuePairale ostatecznie zdecydowałem, że użycie a KeyValuePairdo reprezentowania czegoś, co nie jest parą klucz-wartość, aby uniknąć napisania trywialnie prostej klasy, nie miało sensu. ValueDescriptionKlasa jest tylko 5 linii, a 2 z nich są po prostu {i}
Nick

8

Będziesz musiał utworzyć tablicę wartości w wyliczeniu, które można utworzyć, wywołując System.Enum.GetValues ​​() , przekazując je Typedo wyliczenia, którego chcesz użyć .

Jeśli określisz to dla ItemsSourcewłaściwości, powinna zostać wypełniona wszystkimi wartościami wyliczenia. Prawdopodobnie chcą wiązać SelectedItemsię EffectStyle(zakładając, że jest własnością tego samego wyliczenia i zawiera aktualną wartość).


Dzięki, możesz pokazać pierwszą część kodu? Nie jestem pewien, gdzie przechowywać wartości wyliczenia jako tablicę? Właściwość enum znajduje się w innej klasie. Czy mogę zrobić ten krok GetValues ​​w Xaml?
Joan Venge

4

Wszystkie powyższe posty przeoczyły prostą sztuczkę. Z powiązania SelectedValue można dowiedzieć się, jak automatycznie wypełnić ItemsSource, aby znaczniki XAML były prawidłowe.

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

Na przykład w moim ViewModel mam

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged to mój hak INPC. Twoje mogą się różnić.

Implementacja EnumComboBox jest następująca, ale najpierw potrzebuję małego pomocnika, aby uzyskać ciągi i wartości wyliczenia

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

i główna klasa (uwaga: używam ReactiveUI do przechwytywania zmian właściwości za pośrednictwem 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";

        }
    }
}

Musisz także poprawnie ustawić styl w Generic.XAML, inaczej twoje pudełko nie będzie renderować niczego i wyciągniesz włosy.

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

i to jest to. Można to oczywiście rozszerzyć o obsługę i18n, ale wydłużyłoby post.


3

Wygląda na to, że aplikacje uniwersalne działają nieco inaczej; nie ma całej mocy w pełni funkcjonalnego XAML. Dla mnie zadziałało:

  1. Utworzyłem listę wartości wyliczania jako wyliczenia (nie przekonwertowane na ciągi lub liczby całkowite) i związałem z tym element ComboBox ItemsSource
  2. Następnie mógłbym powiązać ComboBox ItemSelected z moją własnością publiczną, której typem jest dany wyliczenie

Dla zabawy przygotowałem małą szabloną klasę, aby w tym pomóc, i opublikowałem ją na stronach próbek MSDN . Dodatkowe bity pozwalają mi opcjonalnie zastąpić nazwy wyliczeń i pozwolić mi ukryć niektóre wyliczenia. Mój kod wygląda okropnie jak kod Nicka (powyżej), który chciałbym zobaczyć wcześniej.

Uruchamianie próbki;  zawiera wiele podwójnych wiązań do wyliczenia


3

Istnieje wiele doskonałych odpowiedzi na to pytanie i pokornie przesyłam moje. Uważam, że mój jest nieco prostszy i bardziej elegancki. Wymaga tylko konwertera wartości.

Biorąc pod uwagę wyliczenie ...

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
}

i konwerter wartości ...

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

zasoby...

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

Deklaracja XAML ...

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

Zobacz model ...

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

Powstały combobox ...

ComboBox związany z enum


Dla mnie jest to najlepsze rozwiązanie pytania: proste, łatwe do zrozumienia, łatwe do wdrożenia.
Informagic

Problem z tym rozwiązaniem polega na tym, że nie można go zlokalizować.
Robin Davies,

@RobinDavies możesz to zlokalizować. Wymaga niestandardowego opisuAtrybut, którego zbudowałem kilka. Zobacz to pytanie SO dla niektórych pomysłów: 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));
    }
}

Powinieneś rozszerzyć odpowiedź Rogersa i Grega o tego rodzaju konwerter wartości Enum, jeśli wiążesz bezpośrednio z właściwościami modelu obiektu wyliczeniowego.


1

Jeśli wiążesz się z rzeczywistą właściwością wyliczenia na swoim ViewModel, a nie int reprezentacją wyliczenia, sprawy stają się trudne. Stwierdziłem, że konieczne jest powiązanie z reprezentacją ciągu, a NIE wartością int, jak oczekiwano we wszystkich powyższych przykładach.

Możesz stwierdzić, czy tak jest, wiążąc proste pole tekstowe z właściwością, z którą chcesz się połączyć w ViewModel. Jeśli pokazuje tekst, powiąż z łańcuchem. Jeśli pokazuje liczbę, powiąż z wartością. Uwaga: Użyłem Display dwa razy, co normalnie byłoby błędem, ale to jedyny sposób, w jaki działa.

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

Greg


Ta odpowiedź wydaje się niepełna: * Co to jest / core /?
trapicki

1

Podobała mi się odpowiedź tom.maruska , ale musiałem obsługiwać każdy typ wyliczania, który mój szablon może napotkać w czasie wykonywania. W tym celu musiałem użyć powiązania, aby określić typ rozszerzenia znaczników. Byłem w stanie pracować w tej odpowiedzi z nicolay.anykienko, aby wymyślić bardzo elastyczne rozszerzenie znaczników, które działałoby w każdym przypadku, o jakim tylko mogę pomyśleć. Jest zużywany w następujący sposób:

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

Źródło wymienionego powyżej rozszerzenia znaczników:

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

1

Proste i jasne wyjaśnienie: 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>

0

Za pomocą ReactiveUIutworzyłem następujące alternatywne rozwiązanie. To nie jest eleganckie rozwiązanie typu „wszystko w jednym”, ale myślę, że przynajmniej jest czytelne.

W moim przypadku powiązanie listy z enumkontrolką jest rzadkim przypadkiem, więc nie muszę skalować rozwiązania w całej bazie kodu. Jednak kod można uczynić bardziej ogólnym, zmieniając EffectStyleLookup.Itemgo na Object. Przetestowałem to z moim kodem, żadne inne modyfikacje nie są konieczne. Co oznacza, że ​​jedną klasę pomocnika można zastosować do dowolnej enumlisty. Chociaż zmniejszyłoby to jego czytelność -ReactiveList<EnumLookupHelper> nie ma w tym dobrego pierścienia.

Korzystanie z następującej klasy pomocniczej:

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

W ViewModel przekonwertuj listę wyliczeń i ujawnij ją jako właściwość:

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

W ComboBoxużyj SelectedValuePathwłaściwości, aby powiązać z oryginalną enumwartością:

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

W widoku, to pozwala nam wiązać oryginał enumdo SelectedEffectStylew ViewModel, ale wyświetla ToString()wartość w 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) );
});

Myślę, że Twój ViewModel ma błąd. 1) Czy nie powinna to być ReactiveList z EffectStyleLookup ?, 2) Najpierw należy utworzyć pustą ReactiveList <T> (). Następnie dodaj elementy. Wreszcie: ReactiveList <T> jest teraz przestarzały (ale nadal działa). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (lista); Dziękujemy za poświęcenie czasu na pokazanie tego.
user1040323

0

Dodam swój komentarz (niestety w VB, ale koncepcja może być łatwo powielona w C # w mgnieniu oka), ponieważ po prostu musiałem się do tego odwoływać i nie podobały mi się żadne odpowiedzi, ponieważ były zbyt skomplikowane. To nie powinno być takie trudne.

Więc wymyśliłem łatwiejszy sposób. Powiąż enumeratory ze słownikiem. Powiąż ten słownik z Combobox.

Mój combobox:

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

Mój kod. Mam nadzieję, że pomaga to komuś innemu.

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

Odpowiedź Kyrylo jest znacznie prostsza niż twoja - nie rozumiem, co w tym skomplikowanego? Jego wymaga zerowej konwersji kodu.
Johnathon Sullinger

Nie chciałem wkładać całej mojej logiki w ręce XAML. Wolę robić swoją logikę po swojemu (nie zawsze najlepszą), ale pozwala mi to zrozumieć, gdzie i dlaczego coś nie idzie zgodnie z planem. Jego jest mniej skomplikowany, ale logika opiera się na XAML / WPF. Po prostu nie jestem tego fanem. 10 000 sposobów na skórowanie kota, wiesz?
Laki Politis

Słusznie. Ja osobiście wolę korzystać z funkcji, które zostały już wbudowane, ale to tylko moje preferencje;) Każda z nich ma własne!
Johnathon Sullinger

Tak jest! Rozumiem całkowicie. Zostałem zmuszony do tworzenia oprogramowania pochodzącego z tworzenia stron internetowych. Nie byłam na bieżąco z WPF i musiałam się dużo uczyć, tak jak ja. Nadal nie rozumiem wszystkich zawiłości elementów sterujących WPF / XAML, więc znalazłem więcej czkawek niż rozwiązań, w których oczekiwałbym, że wszystko zadziała. Ale doceniam tę rozmowę. Zmusiło mnie to do dalszych badań.
Laki Politis

0

Rozwiązanie Nicka można uprościć bardziej, bez żadnych fantazji, potrzebujesz tylko jednego konwertera:

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

Następnie użyj tego wszędzie tam, gdzie chcesz, aby wyświetlało się pole kombi:

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

0

Nie zalecałbym wdrażania tego w obecnej formie, ale mam nadzieję, że może to zainspirować dobre rozwiązanie.

Powiedzmy, że twój enum to Foo. Następnie możesz zrobić coś takiego.

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

Następnie w Window.Loadmetodzie można załadować wszystkie wyliczenia do wartości, ObservableCollection<FooViewModel>które można ustawić jako DataContext w polu combobox.


0

Po prostu upraszczałem. Utworzyłem listę elementów z wartościami wyliczenia w moim 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();
    }
}

W moim kodzie xaml potrzebuję tylko tego:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.