2009-08-15 20 views
39

Così, il web e StackOverflow, hanno un sacco di belle risposte su come associare una combobox a una proprietà enum in WPF. Ma Silverlight manca tutte le caratteristiche che rendono questo possibile :(Per esempio:.Vincere i ComboBox per enumerare ... in Silverlight!

  1. Non è possibile utilizzare un generico EnumDisplayer in stile IValueConverter che accetta un parametro di tipo, dal momento che Silverlight non supporta x:Type
  2. .
  3. non è possibile utilizzare ObjectDataProvider, come in this approach, dal momento che non esiste in Silverlight.
  4. non è possibile utilizzare un'estensione di markup personalizzato come nel commento il link da # 2, dal momento che le estensioni di markup don' Esistono in Silverlight
  5. Non si può fare una versione di # 1 utilizzando i generici i nondimeno delle proprietà dell'oggetto Type, poiché i generici non sono supportati in XAML (e gli hack per farli funzionare tutti dipendono dalle estensioni di markup, non supportate in Silverlight).

Massive fail!

come la vedo io, l'unico modo per fare questo lavoro è quello di uno

  1. Trucchi e si legano a una proprietà di stringa nel mio ViewModel, il cui/getter setter esegue la conversione, i valori di carico nel ComboBox utilizzando code-behind nella vista.
  2. Creare un'utenza IValueConverter per ogni enumerazione a cui desidero eseguire il binding.

Ci sono alternative che sono più generiche, cioè non comportano la scrittura dello stesso codice più e più volte per ogni enum che voglio? Suppongo che avrei potuto fare soluzione # 2 con una classe generica accettare l'enum come un parametro di tipo, e quindi creare nuove classi per ogni enum Voglio che sono semplicemente

class MyEnumConverter : GenericEnumConverter<MyEnum> {} 

Quali sono i tuoi pensieri, ragazzi?

risposta

35

Agh, ho parlato troppo presto! C'è a perfectly good solution, almeno in Silverlight 3. (Potrebbe essere solo in 3, in quanto this thread indica che un bug relativo a questa roba è stato fissato in Silverlight 3.)

Fondamentalmente, è necessario un unico convertitore per la proprietà ItemsSource , ma può essere completamente generico senza utilizzare nessuno dei metodi proibiti, a condizione che tu lo passi il nome di una proprietà il cui tipo sia MyEnum. E l'associazione di dati a SelectedItem è completamente indolore; nessun convertitore necessario! Bene, almeno è finchè non vuoi stringhe personalizzate per ogni valore enum, ad es. il DescriptionAttribute, hmm ... probabilmente avrà bisogno di un altro convertitore per quello; spero di poterlo fare generico.

Aggiornamento: Ho realizzato un convertitore e funziona! Devo legare a SelectedIndex ora, purtroppo, ma va bene.Utilizzare questi ragazzi:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows.Data; 

namespace DomenicDenicola.Wpf 
{ 
    public class EnumToIntConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      // Note: as pointed out by Martin in the comments on this answer, this line 
      // depends on the enum values being sequentially ordered from 0 onward, 
      // since combobox indices are done that way. A more general solution would 
      // probably look up where in the GetValues array our value variable 
      // appears, then return that index. 
      return (int)value; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      return Enum.Parse(targetType, value.ToString(), true); 
     } 
    } 
    public class EnumToIEnumerableConverter : IValueConverter 
    { 
     private Dictionary<Type, List<object>> cache = new Dictionary<Type, List<object>>(); 

     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      var type = value.GetType(); 
      if (!this.cache.ContainsKey(type)) 
      { 
       var fields = type.GetFields().Where(field => field.IsLiteral); 
       var values = new List<object>(); 
       foreach (var field in fields) 
       { 
        DescriptionAttribute[] a = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false); 
        if (a != null && a.Length > 0) 
        { 
         values.Add(a[0].Description); 
        } 
        else 
        { 
         values.Add(field.GetValue(value)); 
        } 
       } 
       this.cache[type] = values; 
      } 

      return this.cache[type]; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
} 

Con questo tipo di XAML vincolante:

<ComboBox x:Name="MonsterGroupRole" 
      ItemsSource="{Binding MonsterGroupRole, 
           Mode=OneTime, 
           Converter={StaticResource EnumToIEnumerableConverter}}" 
      SelectedIndex="{Binding MonsterGroupRole, 
            Mode=TwoWay, 
            Converter={StaticResource EnumToIntConverter}}" /> 

E questo tipo di risorsa dichiarazione XAML:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:ddwpf="clr-namespace:DomenicDenicola.Wpf"> 
    <Application.Resources> 
     <ddwpf:EnumToIEnumerableConverter x:Key="EnumToIEnumerableConverter" /> 
     <ddwpf:EnumToIntConverter x:Key="EnumToIntConverter" /> 
    </Application.Resources> 
</Application> 

Qualsiasi commento sarebbe apprezzato, come ho' m un po 'di XAML/Silverlight/WPF/etc. novizio. Ad esempio, lo EnumToIntConverter.ConvertBack sarà lento, quindi dovrei considerare l'utilizzo di una cache?

+0

Sicuramente cache tutte le cose che si sta facendo con l'oggetto tipo (ad esempio GetFields()) in quanto è la riflessione e generalmente considerato lento (anche se ovviamente dipende da l'uso della riflessione della tua applicazione). A parte quel bel lavoro! –

+0

molto utile. Grazie. hai mai provato ad estendere questo per una facile traduzione dei valori - come OrderStatus.NewOrder su "New Order"? –

+0

Infatti, il codice sopra analizzerà qualsiasi 'DescriptionAttributes' che aggiungi ai campi enum :). – Domenic

5

C'è un altro modo per associare ComboBox alle enumerazioni senza la necessità di un convertitore personalizzato per l'elemento selezionato. È possibile controllare a

http://charlass.wordpress.com/2009/07/29/binding-enums-to-a-combobbox-in-silverlight/

Non usa le DescriptionAttributes .... ma funziona perfettamente per me, quindi credo che dipende dallo scenario verrà utilizzato

+0

Link utile ma è meglio includere parti significative nella risposta nel caso in cui il collegamento venga interrotto. – Fedor

5

trovo che un semplice incapsulamento dei dati enumerati è molto più facile da usare.

public ReadOnly property MonsterGroupRole as list(of string) 
    get 
    return [Enum].GetNames(GetType(GroupRoleEnum)).Tolist 
    End get 
End Property 

private _monsterEnum as GroupRoleEnum 
Public Property MonsterGroupRoleValue as Integer 
    get 
    return _monsterEnum 
    End get 
    set(value as integer) 
    _monsterEnum=value 
    End set 
End Property 

...

<ComboBox x:Name="MonsterGroupRole" 
     ItemsSource="{Binding MonsterGroupRole, 
          Mode=OneTime}" 
     SelectedIndex="{Binding MonsterGroupRoleValue , 
           Mode=TwoWay}" /> 

E questo sarà completamente eliminare la necessità di un convertitore ... :)

+0

lol questo è solo la creazione manuale di un convertitore che funziona solo per uno specifico enum. – Domenic

1

Ecco la stessa impostazione per un Windows 8.1/Windows Phone App universale , principali variazioni sono: -

  • Manca DescriptionAttribute nel quadro (o almeno io non può trovarlo)
  • 012.
  • Le differenze nel modo in cui funziona la riflessione (utilizzando i campi TypeInfo.Declared)

Sembra che l'ordine del XAML è troppo importante, ho dovuto mettere ItemsSource prima SelectedIndex altrimenti non ha chiamato l'ItemsSource vincolante esempio

<ComboBox 
ItemsSource="{Binding Path=MyProperty,Mode=OneWay, Converter={StaticResource EnumToIEnumerableConverter}}" 
SelectedIndex="{Binding Path=MyProperty, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}" 
/> 

codice qui sotto

namespace MyApp.Converters 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Reflection; 
    using Windows.UI.Xaml.Data; 
    public class EnumToIntConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, string language) 
     { 
      // Note: as pointed out by Martin in the comments on this answer, this line 
      // depends on the enum values being sequentially ordered from 0 onward, 
      // since combobox indices are done that way. A more general solution would 
      // probably look up where in the GetValues array our value variable 
      // appears, then return that index. 
      return (int) value; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, string language) 
     { 
      return value; 
     } 
    } 

    public class EnumToIEnumerableConverter : IValueConverter 
    { 
     private readonly Dictionary<TypeInfo, List<object>> _cache = new Dictionary<TypeInfo, List<object>>(); 

     public object Convert(object value, Type targetType, object parameter, string language) 
     { 
      var type = value.GetType().GetTypeInfo(); 
      if (!_cache.ContainsKey(type)) 
      { 
       var fields = type.DeclaredFields.Where(field => field.IsLiteral); 
       var values = new List<object>(); 
       foreach (var field in fields) 
       { 
        var a = (DescriptionAttribute[]) field.GetCustomAttributes(typeof(DescriptionAttribute), false); 
        if (a != null && a.Length > 0) 
        { 
         values.Add(a[0].Description); 
        } 
        else 
        { 
         values.Add(field.GetValue(value)); 
        } 
       } 
       _cache[type] = values; 
      } 
      return _cache[type]; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, string language) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
    [AttributeUsage(AttributeTargets.Field)] 
    public class DescriptionAttribute : Attribute 
    { 
     public string Description { get; private set; } 

     public DescriptionAttribute(string description) 
     { 
      Description = description; 
     } 
    } 
} 
Problemi correlati