Come associare un enum a un controllo combobox in WPF?


182

Sto cercando di trovare un semplice esempio in cui gli enumerati sono mostrati così come sono. Tutti gli esempi che ho visto cercano di aggiungere stringhe di visualizzazione dall'aspetto gradevole, ma non voglio quella complessità.

Fondamentalmente ho una classe che contiene tutte le proprietà che associo, impostando prima DataContext su questa classe e quindi specificando l'associazione come questa nel file xaml:

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

Ma questo non mostra i valori enum negli ComboBoxelementi as.


9
Ecco cosa stai cercando: WPF ObjectDataProvider - Associare Enum a ComboBox Puoi anche scaricare l'esempio completo del codice sorgente da lì.

La risposta migliore a mio parere è in: stackoverflow.com/questions/58743/...
gimpy

Risposte:


306

Puoi farlo dal codice inserendo il seguente codice nel Loadedgestore eventi Window , ad esempio:

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

Se è necessario associarlo in XAML, è necessario utilizzare ObjectDataProviderper creare l'oggetto disponibile come origine di associazione:

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

Attira l'attenzione sul prossimo codice:

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

Guida su come mappare lo spazio dei nomi e l'assemblaggio che puoi leggere su MSDN .


1
Esempio testato dal primo collegamento, funziona bene. Vedi il codice aggiunto e commenta nella mia risposta.
Kyrylo M,

1
Trovato il tuo problema sui forum MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Prova a pulire e ricostruire il progetto. Probabilmente dovresti chiedere questo problema qui su un'altra domanda. Questo è l'unico che posso consigliare ... Ad ogni modo, l'esempio mostrato è corretto.
Kyrylo M,

1
Grazie, è bizzarro ma ho visto cose simili con la follia di WPF. Lo farà e ti farà sapere. A proposito, questo è lo stesso problema descritto qui: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge,

2
È necessario aggiungere un riferimento ad esso e aggiungere xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"XAML per usarlo. Ecco la guida: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M

4
Puoi usare strumenti come ReSharper. Analizza tutti gli assiemi referenziati e fornisce suggerimenti su cosa includere. Non c'è bisogno di scrivere: basta selezionare tra le opzioni.
Kyrylo M,

117

Mi piace che tutti gli oggetti che rilevo siano definiti nel mio ViewModel, quindi cerco di evitare di usarlo <ObjectDataProvider>in xaml quando possibile.

La mia soluzione non utilizza alcun dato definito nella vista e nessun code-behind. Solo un DataBinding, un ValueConverter riutilizzabile, un metodo per ottenere una raccolta di descrizioni per qualsiasi tipo di Enum e una singola proprietà nel ViewModel a cui associarsi.

Quando voglio associare un Enuma un ComboBoxtesto che voglio visualizzare non corrisponde mai ai valori di Enum, quindi uso l' [Description()]attributo per dargli il testo che in realtà voglio vedere in ComboBox. Se avessi un enum di giorni della settimana, sarebbe simile a questo:

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

Per prima cosa ho creato una classe di supporto con un paio di metodi per gestire gli enum. Un metodo ottiene una descrizione per un valore specifico, l'altro metodo ottiene tutti i valori e le relative descrizioni per un tipo.

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

Successivamente, creiamo un ValueConverter. Ereditando daMarkupExtension semplifica l'utilizzo in XAML, quindi non è necessario dichiararlo come risorsa.

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

La mia ViewModelha bisogno di solo 1 struttura che il mio Viewpuò legarsi al sia per l' SelectedValuee ItemsSourcedel combobox:

private DayOfWeek dayOfWeek;

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

E infine per associare la ComboBoxvista (usando il ValueConverternell'associazione ItemsSource) ...

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

Per implementare questa soluzione devi solo copiare la mia EnumHelperclasse e EnumToCollectionConverterclasse. Lavoreranno con qualsiasi enumerazione. Inoltre, non l'ho incluso qui, ma la ValueDescriptionclasse è solo una semplice classe con 2 proprietà di oggetto pubblico, una chiamata Value, una chiamata Description. Puoi crearlo tu stesso o puoi cambiare il codice per usare un Tuple<object, object>oKeyValuePair<object, object>


9
Per farlo funzionare, ho dovuto creare una ValueDescriptionclasse che avesse proprietà pubbliche per ValueeDescription
Perchik,

4
Sì, puoi anche cambiare questo codice per usare una Tuple<T1, T2>oo KeyValuePair<TKey, TValue>invece della ValueDescriptionclasse e quindi non dovrai crearne una tua.
Nick

Avevo bisogno di implementare OnPropertyChanged (o l'equivalente) per entrambe le proprietà ViewModel, non solo per SelectedClass.
Sarà il

Non è necessario implementare OnPropertyChanged per la proprietà che restituisce l'elenco. L'elenco viene generato dai valori in un Enum. Non cambierà mai durante il runtime e quando non cambia mai, non deve mai notificare a nessuno che è cambiato. Inoltre, con la versione aggiornata, la proprietà list non è nemmeno necessaria.
Nick,

In che modo ItemSource e SelectedValue della casella combinata hanno la stessa proprietà. ItemsSource non deve essere un elenco? Oh, vedo, è perché EnumHelper crea un elenco di oggetti. questo in realtà rende il mio ViewModel più semplice poiché non devo mantenere un elenco separato di oggetti per popolare ItemSource.
Stealth Rabbi

46

Ho usato un'altra soluzione usando MarkupExtension.

  1. Ho creato una classe che fornisce gli articoli fonte:

    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. È quasi tutto ... Ora usalo in XAML:

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


1
@Nick: la risposta accettata si riferisce anche a enum (o modello come hai detto) in xaml. La tua soluzione sta creando 2 proprietà e un campo di supporto nel modello di visualizzazione, che non mi è piaciuto (regola DRY). E, naturalmente, non è necessario utilizzare e.ToString()per il nome visualizzato. Puoi usare il tuo traduttore, parser di attributi descrittivi, qualunque cosa.
tom.maruska,

2
@ tom.maruska Non sto cercando di entrare nella mia risposta contro la tua risposta, ma da quando l'hai tirata su, avere 2 proprietà non viola la regola DRY quando sono 2 proprietà distinte che hanno scopi diversi. E la tua risposta richiederebbe anche l'aggiunta di una proprietà (hai anche chiamato tu stesso questa proprietà {Binding Path=WhereEverYouWant}) e se vuoi che supporti l'associazione bidirezionale avrai anche un campo di supporto per essa. Quindi non stai sostituendo 2 proprietà e 1 campo di supporto in questo modo, stai sostituendo solo 1 proprietà di sola riga.
Nick,

@Nick Sì, hai ragione su quella proprietà e sul campo di supporto :)
tom.maruska

25

Usa ObjectDataProvider:

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

e quindi associare a risorsa statica:

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

basato su questo articolo


4
Soluzione perfettamente semplice. Spazio dei nomi per System come nella risposta di kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite

Funziona bene nei progetti WPF di Visual Studio 2017.
Sorush,

11

La risposta di Nick mi ha davvero aiutato, ma mi sono reso conto che poteva essere leggermente modificato, per evitare una classe aggiuntiva, ValueDescription. Ho ricordato che esiste già una classe KeyValuePair nel framework, quindi può essere utilizzata al suo posto.

Il codice cambia solo leggermente:

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

e infine XAML:

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

Spero che questo sia utile per gli altri.


La mia prima implementazione ha usato un KeyValuePairma alla fine ho deciso di usare a KeyValuePairper rappresentare qualcosa che non è una coppia chiave-valore solo per evitare di scrivere una classe banalmente semplice non aveva molto senso. La ValueDescriptionclasse è composta solo da 5 righe e 2 sono solo {e}
Nick

8

Dovrai creare un array di valori nell'enum, che può essere creato chiamando System.Enum.GetValues ​​() , passandogli ilType l'enum di cui desideri gli elementi.

Se lo specifichi per la ItemsSourceproprietà, allora dovrebbe essere popolato con tutti i valori dell'enum. Probabilmente si desidera associare SelectedItema EffectStyle(ammesso che sia una proprietà della stessa enum, e contiene il valore corrente).


Grazie, puoi mostrare la prima parte del codice per favore? Non sono sicuro dove archiviare i valori enum come array? La proprietà enum si trova in un'altra classe. Posso fare questo passo GetValues ​​all'interno di xaml?
Joan Venge,

4

Tutti i post sopra hanno perso un semplice trucco. È possibile dall'associazione di SelectedValue per scoprire come popolare AUTOMATICAMENTE l'oggetto ItemsSource in modo che il markup XAML sia giusto.

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

Ad esempio nel mio ViewModel che ho

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged è il mio hook INPC. Il tuo potrebbe essere diverso.

L'implementazione di EnumComboBox è la seguente, ma prima avrò bisogno di un piccolo aiuto per ottenere le stringhe e i valori di enumerazione

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

e la classe principale (nota che sto usando ReactiveUI per l'aggancio delle modifiche alle proprietà tramite 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";

        }
    }
}

Devi anche impostare lo stile correttamente in Generic.XAML o la tua scatola non renderà nulla e ti toglierai i capelli.

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

e questo è quello. Questo potrebbe ovviamente essere esteso per supportare i18n ma renderebbe il post più lungo.


3

Le app universali sembrano funzionare in modo leggermente diverso; non ha tutto il potere di XAML completo. Ciò che ha funzionato per me è:

  1. Ho creato un elenco dei valori di enum come enum (non convertito in stringhe o in numeri interi) e ho associato il ComboBox ItemsSource a quello
  2. Quindi potrei associare il ComboBox ItemSelected alla mia proprietà pubblica il cui tipo è l'enum in questione

Solo per divertimento ho preparato una piccola classe per aiutare con questo e l'ho pubblicato nelle pagine degli esempi MSDN . I bit extra mi permettono di sovrascrivere facoltativamente i nomi degli enum e di lasciarmi nascondere alcuni degli enum. Il mio codice sembra orribile come quello di Nick (sopra), che vorrei aver visto prima.

Esecuzione del campione;  include più attacchi twoway all'enum


3

Ci sono molte risposte eccellenti a questa domanda e invio umilmente la mia. Trovo che il mio sia un po 'più semplice ed elegante. Richiede solo un convertitore di valore.

Dato un enum ...

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
}

e un convertitore di valore ...

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

risorse ...

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

Dichiarazione XAML ...

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

Visualizza modello ...

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

Casella combinata risultante ...

ComboBox associato a enum


Per me, questa è la migliore soluzione alla domanda: semplice, facile da capire, semplice da implementare.
Informagic

Il problema con questa soluzione è che non è localizzabile.
Robin Davies il

@RobinDavies puoi localizzarlo. Richiede una descrizione personalizzata Attributo del quale ne ho creati alcuni. Vedere questa domanda SO per alcune idee: 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));
    }
}

Dovresti estendere la risposta di Rogers e Greg con questo tipo di convertitore di valori Enum, se stai legando direttamente alle proprietà del modello di oggetti enum.


1

Se si esegue il binding a una proprietà enum effettiva sul ViewModel, non a una rappresentazione int di un enum, le cose si complicano. Ho trovato che è necessario associare la rappresentazione di stringa, NON il valore int come previsto in tutti gli esempi precedenti.

Puoi capire se questo è il caso associando una semplice casella di testo alla proprietà a cui desideri associare il ViewModel. Se mostra del testo, associa alla stringa. Se mostra un numero, associa al valore. Nota Ho usato Display due volte, che normalmente sarebbe un errore, ma è l'unico modo in cui funziona.

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

Greg


Questa risposta sembra incompleta: * Che cos'è / core /?
trapicki,

1

Mi è piaciuta la risposta di tom.maruska , ma dovevo supportare qualsiasi tipo di enum che il mio template potesse incontrare in fase di esecuzione. Per questo, ho dovuto usare un'associazione per specificare il tipo per l'estensione di markup. Sono stato in grado di lavorare su questa risposta da nicolay.anykienko per trovare un'estensione di markup molto flessibile che avrebbe funzionato in ogni caso che mi viene in mente. Si consuma così:

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

La fonte per l'estensione di markup mashed cui sopra:

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

Spiegazione semplice e chiara: 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

Utilizzando ReactiveUI, ho creato la seguente soluzione alternativa. Non è un'elegante soluzione all-in-one, ma penso che sia almeno leggibile.

Nel mio caso, associare un elenco enuma un controllo è un caso raro, quindi non ho bisogno di ridimensionare la soluzione sulla base di codice. Tuttavia, il codice può essere reso più generico cambiando EffectStyleLookup.Itemin un Object. L'ho provato con il mio codice, non sono necessarie altre modifiche. Ciò significa che una classe di supporto potrebbe essere applicata a qualsiasi enumelenco. Sebbene ciò ridurrebbe la sua leggibilità -ReactiveList<EnumLookupHelper> non ha un ottimo suono.

Utilizzando la seguente classe di supporto:

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

In ViewModel, converti l'elenco di enumerazioni ed esponilo come proprietà:

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 ComboBox, utilizzare la SelectedValuePathproprietà, per associare al enumvalore originale :

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

Nella vista, ciò ci consente di associare l'originale enuma SelectedEffectStylenel ViewModel, ma mostra il ToString()valore in 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) );
});

Penso che il tuo ViewModel abbia un errore. 1) Non dovrebbe essere un ReactiveList di EffectStyleLookup ?, 2) Dovresti prima creare un ReactiveList vuoto <T> (). Quindi aggiungi gli articoli. Infine: ReactiveList <T> è ora deprecato (ma funziona ancora). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (lista); Grazie per il tempo dedicato a dimostrarlo.
user1040323,

0

Sto aggiungendo il mio commento (in VB, purtroppo, ma il concetto può essere facilmente replicato su C # in un batter d'occhio), perché dovevo solo fare riferimento a questo e non mi piaceva nessuna delle risposte perché erano troppo complesse. Non dovrebbe essere così difficile.

Quindi ho trovato un modo più semplice. Associare gli enumeratori a un dizionario. Associa quel dizionario alla casella combinata.

La mia casella combinata:

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

Il mio code-behind. Spero che questo aiuti qualcun altro.

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

La risposta di Kyrylo è molto più semplice della tua: non capisco cosa sia complicato? Richiede la conversione zero nel codice.
Johnathon Sullinger,

Non volevo mettere tutta la mia logica nelle mani di XAML. Preferisco fare la mia logica a modo mio (non sempre nel modo migliore), ma mi permette di capire dove e perché qualcosa non va secondo i piani. Il suo è meno complicato, ma si affida a XAML / WPF per fare la logica. Non ne sono un fan. 10.000 modi per scuoiare un gatto, sai?
Laki Politis,

Giusto. Personalmente preferisco usare funzionalità già costruite, pronte per l'uso, per me, ma questa è solo la mia preferenza;) A ognuna ce ne sono!
Johnathon Sullinger,

Si signore! Capisco perfettamente. Sono stato costretto allo sviluppo del software proveniente dallo sviluppo web. Non sono stato così aggiornato su WPF e ho dovuto imparare molto come sono andato avanti. Non capisco ancora tutte le complessità dei controlli WPF / XAML, quindi ho trovato più singhiozzi che soluzioni sul modo in cui mi aspetto che le cose funzionino. Ma apprezzo questa conversazione. Mi ha fatto fare qualche ricerca in più.
Laki Politis,

0

Il solutuion di Nick può essere semplificato di più, senza nulla di speciale, avresti bisogno di un solo convertitore:

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

Quindi lo usi dove vuoi che appaia la tua casella combinata:

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

0

Non consiglierei di implementarlo così com'è, ma spero che questo possa ispirare una buona soluzione.

Supponiamo che il tuo enum sia Foo. Quindi puoi fare qualcosa del genere.

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

Quindi sul Window.Loadmetodo è possibile caricare tutti gli enum su un valore ObservableCollection<FooViewModel>che è possibile impostare come DataContext della casella combinata.


0

L'ho semplicemente tenuto semplice. Ho creato un elenco di elementi con i valori enum nel mio 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();
    }
}

Nel mio codice xaml ho solo bisogno di questo:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.