Associazione di dati di una proprietà enum a un controllo ComboBox in WPF

Ad esempio, prendi il seguente codice:

public enum ExampleEnum { FooBar, BarFoo } public class ExampleClass : INotifyPropertyChanged { private ExampleEnum example; public ExampleEnum ExampleProperty { get { return example; } { /* set and notify */; } } } 

Voglio un database della proprietà ExampleProperty a un ComboBox, in modo che mostri le opzioni “FooBar” e “BarFoo” e funzioni in modalità TwoWay. In modo ottimale voglio che la mia definizione di ComboBox assomigli a qualcosa del genere:

  

Attualmente ho gestori per gli eventi ComboBox.SelectionChanged ed ExampleClass.PropertyChanged installati nella mia finestra in cui eseguo manualmente l’associazione.

C’è un modo migliore o un qualche tipo di canonico? Utilizzeresti solitamente i convertitori e in che modo inseriresti il ​​ComboBox con i valori corretti? Non voglio nemmeno iniziare con i18n adesso.

modificare

Quindi una risposta è stata data: come faccio a compilare il ComboBox con i valori corretti.

Recupera i valori Enum come elenco di stringhe tramite ObjectDataProvider dal metodo statico Enum.GetValues:

        

Questo posso usare come ItemsSource per il mio ComboBox:

  

È ansible creare un’estensione di markup personalizzata.

Esempio di utilizzo:

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

E l’implementazione …

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

Nel viewmodel puoi avere:

  public MyEnumType SelectedMyEnumType { get { return _selectedMyEnumType; } set { _selectedMyEnumType = value; OnPropertyChanged("SelectedMyEnumType"); } } public IEnumerable MyEnumTypeValues { get { return Enum.GetValues(typeof(MyEnumType)) .Cast(); } } 

In XAML, ItemSource si collega a MyEnumTypeValues ​​e SelectedItem si collega a SelectedMyEnumType.

  

Preferisco non usare il nome di enum nell’IU. Preferisco usare un valore diverso per l’utente ( DisplayMemberPath ) e diverso per valore (enum in questo caso) ( SelectedValuePath ). Questi due valori possono essere impacchettati in KeyValuePair e memorizzati nel dizionario.

XAML

  

C #

 public Dictionary ExampleEnumsWithCaptions { get { return new Dictionary() // Fix. Each time new dict.? { {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 */; } } 

MODIFICA: compatibile con il modello MVVM.

Non so se è ansible solo in XAML ma prova quanto segue:

Dai un nome al tuo ComboBox in modo da poterlo accedere nel codebehind: “typesComboBox1”

Ora prova il seguente

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

Sulla base della risposta accettata ma ora cancellata fornita da ageektrapped, ho creato una versione snellita senza alcune delle funzionalità più avanzate. Tutto il codice è incluso qui per permetterti di copiarlo e incollarlo e non essere bloccato da link-rot.

Uso System.ComponentModel.DescriptionAttribute che è realmente destinato alle descrizioni dei tempi di progettazione. Se non ti piace usare questo attributo puoi crearne uno tuo, ma penso che l’uso di questo attributo abbia davvero il compito. Se non si utilizza l’attributo, il nome imposterà automaticamente il nome del valore enum nel codice.

 public enum ExampleEnum { [Description("Foo Bar")] FooBar, [Description("Bar Foo")] BarFoo } 

Ecco la class utilizzata come fonte degli elementi:

 public class EnumItemsSource : Collection, IValueConverter { Type type; IDictionary valueToNameMap; IDictionary 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; } } 

Puoi usarlo in XAML come questo:

     

Usa ObjectDataProvider:

      

e quindi legarsi alla risorsa statica:

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

Trova questa soluzione in questo blog

puoi considerare qualcosa del genere:

  1. definire uno stile per il blocco di testo o qualsiasi altro controllo che si desidera utilizzare per visualizzare l’enumerazione:

        
  2. definisci il tuo stile per ComboBoxItem

       
  3. aggiungi una casella combinata e caricala con i tuoi valori enum:

         Value1    

se il tuo enum è grande, puoi ovviamente fare lo stesso nel codice, risparmiando un sacco di digitazione. Mi piace questo approccio, poiché semplifica la localizzazione: si definiscono tutti i modelli una sola volta e quindi si aggiornano solo i file di risorse stringa.

Ecco una soluzione generica che utilizza un metodo di supporto. Questo può anche gestire un enum di qualsiasi tipo sottostante (byte, sbyte, uint, long, ecc.)

Metodo di supporto:

 static IEnumerable GetEnum() { 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 

Visualizza modello:

 public IEnumerable EnumSearchTypes { get { return GetEnum(); } }//property 

Casella combinata:

  

Il mio modo preferito per farlo è con un ValueConverter modo che ValueConverter e SelectedValue si leghino entrambi alla stessa proprietà. Questo non richiede proprietà aggiuntive per mantenere il tuo ViewModel bello e pulito.

  

E la definizione del convertitore:

 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))] public class EnumToCollectionConverter : MarkupExtension, IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return Enum.GetValues(value.GetType()) .Cast() .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; } } 

Questo convertitore funzionerà con qualsiasi enum. ValueDescription è solo una semplice class con una proprietà Value e una proprietà Description . Si potrebbe facilmente usare una Tuple con Item2 e Item2 , o KeyValuePair con Key e Value invece di Value and Description o qualsiasi altra class di tua scelta, purché possa contenere un valore enum e una descrizione stringa di quel valore enum.

Questa è una risposta specifica DevExpress basata sulla risposta più votata di Gregor S. (attualmente ha 128 voti).

Ciò significa che possiamo mantenere lo stile coerente su tutta l’applicazione:

inserisci la descrizione dell'immagine qui

Sfortunatamente, la risposta originale non funziona con un ComboBoxEdit di DevExpress senza alcune modifiche.

Innanzitutto, XAML per ComboBoxEdit :

  

Inutile dire che dovrai indicare xamlExtensions nello spazio dei nomi che contiene la class di estensione XAML (che è definita di seguito):

 xmlns:xamlExtensions="clr-namespace:XamlExtensions" 

E dobbiamo indicare myEnum nello spazio dei nomi che contiene l’enum:

 xmlns:myEnum="clr-namespace:MyNamespace" 

Quindi, l’enum:

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

Il problema con XAML è che non possiamo usare SelectedItemValue , in quanto ciò genera un errore in quanto il setter non è accessibile (un po ‘di svista da parte vostra, DevExpress ). Quindi dobbiamo modificare il nostro ViewModel per ottenere il valore direttamente dall’object:

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

Per completezza, ecco l’estensione XAML dalla risposta originale (leggermente rinominata):

 namespace XamlExtensions { ///  /// 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. ///  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 } } 

Disclaimer: non ho alcuna affiliazione con DevExpress. Telerik è anche una grande biblioteca.

Se si utilizza MVVM, in base alla risposta di @rudigrobler, è ansible effettuare le seguenti operazioni:

Aggiungi la seguente proprietà alla class ViewModel

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

Quindi in XAML fai quanto segue:

  

Prova a usare

  

Ho creato un progetto CodePlex open source che esegue questa operazione. Puoi scaricare il pacchetto NuGet da qui .