Come associare un enum a un controllo a combobox in WPF? [c#]


Answers

Mi piace per tutti gli oggetti che sono vincolante da definire nel mio ViewModel , quindi cerco di evitare l'uso di <ObjectDataProvider> nella xaml quando possibile.

Quando voglio associare un Enum a un ComboBox il testo che voglio visualizzare non corrisponde mai ai valori Enum , quindi uso l'attributo [Description()] per dargli il testo che voglio effettivamente vedere nel ComboBox . Se avessi un enum di classi di personaggi in un gioco, sarebbe simile a questo:

public enum PlayerClass
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Shadow Knight")]
  SHADOW_KNIGHT,
  ...
}

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

public static class EnumHelper
{
  public static string Description(this Enum eValue)
  {
    var nAttributes = eValue.GetType().GetField(eValue.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
    if (nAttributes.Any())
      return (nAttributes.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 oTI = CultureInfo.CurrentCulture.TextInfo;
    return oTI.ToTitleCase(oTI.ToLower(eValue.ToString().Replace("_", " ")));
  }

  public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
  {
    if (!t.IsEnum)
      throw new ArgumentException("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 . Ereditare da MarkupExtension rende più facile 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;
  }
}

My ViewModel richiede solo 1 proprietà a cui può collegarsi la mia View sia per SelectedValue che per ItemsSource della casella combinata:

private PlayerClass p_eClass;

public PlayerClass SelectedClass
{
  get { return p_eClass; }
  set
  {
    if (p_eClass != value)
    {
      p_eClass = value;
      OnPropertyChanged(nameof(SelectedClass));
    }
  }
}

E infine per associare la vista ComboBox (usando ValueConverter nel binding ValueConverter ) ...

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

Per implementare questa soluzione è sufficiente copiare la mia classe EnumToCollectionConverter e la classe EnumToCollectionConverter . Lavoreranno con qualsiasi enumerazione. Inoltre, non l'ho incluso qui, ma la classe ValueDescription è solo una semplice classe con 2 proprietà dell'oggetto pubblico, una chiamata Value , una chiamata Description . Puoi crearlo da solo oppure puoi cambiare il codice per usare una Tuple<object, object> o KeyValuePair<object, object>

Question

Sto cercando di trovare un semplice esempio in cui le enumerazioni sono mostrate così come sono. Tutti gli esempi che ho visto cercano di aggiungere stringhe di visualizzazione di aspetto piacevole, ma non voglio quella complessità.

Fondamentalmente ho una classe che contiene tutte le proprietà che leghiamo, 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 nel ComboBox come oggetti.




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 legarsi alla risorsa statica:

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



La risposta di Nick mi ha davvero aiutato, ma ho capito che potrebbe essere leggermente modificato, per evitare una classe extra, ValueDescription. Mi sono ricordato che esiste già una classe KeyValuePair nel framework, quindi può essere utilizzata al suo posto.

Il codice cambia 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 lo XAML:

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

Spero che questo sia utile per gli altri.




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 Rogers e la risposta di Greg con questo tipo di convertitore di valori Enum, se stai vincolando direttamente alle proprietà del modello di oggetti enum.




Le app universali sembrano funzionare in modo un po 'diverso; non ha tutta la potenza del XAML completo. Quello che ha funzionato per me è:

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

Solo per divertimento ho montato un piccolo modulo per aiutarlo e l'ho pubblicato sulle pagine degli MSDN Samples . I bit extra mi consentono di ignorare facoltativamente i nomi delle enumerazioni e di farmi nascondere alcune enumerazioni. Il mio codice sembra terribile come quello di Nick (sopra), che vorrei aver visto prima.




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

Così ho trovato un modo più semplice. Associare gli enumeratori a un dizionario. Collega quel dizionario al Combobox.

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