2009-05-27 9 views
13

Bueno, el problema es que tengo esta enumeración, PERO no quiero que combobox muestre los valores de la enumeración. Esta es la enumeración:WPF binding ComboBox to enum (con un giro)

public enum Mode 
    { 
     [Description("Display active only")] 
     Active, 
     [Description("Display selected only")] 
     Selected, 
     [Description("Display active and selected")] 
     ActiveAndSelected 
    } 

Así, en el cuadro combinado en lugar de mostrar activo, seleccionan o ActiveAndSelected, quiero mostrar la propiedaddescripcion para cada valor de la enumeración. Tengo un método de extensión llamado GetDescription() para la enumeración:

public static string GetDescription(this Enum enumObj) 
     { 
      FieldInfo fieldInfo = 
       enumObj.GetType().GetField(enumObj.ToString()); 

      object[] attribArray = fieldInfo.GetCustomAttributes(false); 

      if (attribArray.Length == 0) 
      { 
       return enumObj.ToString(); 
      } 
      else 
      { 
       DescriptionAttribute attrib = 
        attribArray[0] as DescriptionAttribute; 
       return attrib.Description; 
      } 
     } 

Entonces, ¿hay alguna manera de obligar a la enumeración de ComboBox Y le mostrará su contenido con el método de extensión GetDescription?

Gracias!

Respuesta

6

Me gusta tu forma de pensar. Pero GetCustomAttributes usa la reflexión. ¿Qué va a hacer esto con tu rendimiento?

Control hacia fuera este mensaje: WPF - Viendo enumeraciones en el control ComboBox http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html

+16

Amigo, la reflexión no es _that_ lento, sobre todo en comparación con el tiempo que toma para mostrar una interfaz gráfica de usuario. No esperaría que sea un problema. –

+0

Bueno, no tome mi palabra para eso. La publicación mencionada anteriormente dice que es una preocupación. –

+3

Pero no cita ningún resultado de perfil. El autor estaba preocupado por eso, pero eso no significa que realmente haya sido un problema. –

3

Preguntas del uso de la reflexión y atributos a un lado, hay algunas maneras que usted puede hacer esto, pero creo que la mejor forma de hacerlo es simplemente crear un poco de vista de clase modelo que envuelve el valor de la enumeración:

public class ModeViewModel : ViewModel 
{ 
    private readonly Mode _mode; 

    public ModeViewModel(Mode mode) 
    { 
     ... 
    } 

    public Mode Mode 
    { 
     get { ... } 
    } 

    public string Description 
    { 
     get { return _mode.GetDescription(); } 
    } 
} 

Alternativamente, usted podría ver en el uso ObjectDataProvider.

3

le sugiero que utilice una extensión de marcado que ya había publicado here, con sólo una pequeña modificación:

[MarkupExtensionReturnType(typeof(IEnumerable))] 
public class EnumValuesExtension : MarkupExtension 
{ 
    public EnumValuesExtension() 
    { 
    } 

    public EnumValuesExtension(Type enumType) 
    { 
     this.EnumType = enumType; 
    } 

    [ConstructorArgument("enumType")] 
    public Type EnumType { get; set; } 

    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
     if (this.EnumType == null) 
      throw new ArgumentException("The enum type is not set"); 
     return Enum.GetValues(this.EnumType).Select(o => GetDescription(o)); 
    } 
} 

A continuación, puede utilizarlo como esa:

<ComboBox ItemsSource="{local:EnumValues local:Mode}"/> 

EDIT: el método que sugerido se vinculará a una lista de cadenas, lo cual no es deseable ya que queremos que SelectedItem sea del tipo Modo. Sería mejor eliminar la parte .Select (...) y usar un enlace con un convertidor personalizado en ItemTemplate.

+0

¿No haría esto que SelectedItem del cuadro combinado fuera "Mostrar solo activo" en lugar de Modo.Active? Parece un efecto secundario indeseable para mí. –

+0

Entonces, ¿eso significa que con este enfoque no podré establecer el elemento seleccionado a lo que el objeto con la enumeración ha seleccionado actualmente? – Carlo

+0

@Joe: sí, tienes razón ... eso es un problema de hecho. Actualizaré mi respuesta –

19

Sugeriría un DataTemplate y un ValueConverter. Eso le permitirá personalizar la forma en que se muestra, pero aún podrá leer la propiedad SelectedItem del combobox y obtener el valor enum real.

ValueConverters requiere una gran cantidad de código repetitivo, pero no hay nada demasiado complicado aquí. En primer lugar se crea la clase ValueConverter:

public class ModeConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, 
     CultureInfo culture) 
    { 
     return ((Mode) value).GetDescription(); 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, 
     CultureInfo culture) 
    { 
     throw new NotSupportedException(); 
    } 
} 

Dado que sólo se va a convertir valores de enumeración de cadenas (por pantalla), no es necesario ConvertBack - eso es sólo para los escenarios de enlace de dos vías.

Luego, coloque una instancia de la ValueConverter en sus recursos, con algo como esto:

<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1"> 
    <Window.Resources> 
     <WpfApplication1:ModeConverter x:Key="modeConverter"/> 
    </Window.Resources> 
    .... 
</Window> 

Entonces estás listo para dar el ComboBox un DisplayTemplate que da formato a sus elementos mediante el ModeConverter:

<ComboBox Name="comboBox" ...> 
    <ComboBox.ItemTemplate> 
     <DataTemplate> 
      <TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/> 
     </DataTemplate> 
    </ComboBox.ItemTemplate> 
</ComboBox> 

Para probar esto, lancé una etiqueta también, que me mostraría el valor real de SelectedItem, y efectivamente mostró que SelectedItem es la enumeración en lugar del texto de visualización, que es lo que me gustaría:

<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/> 
+0

Amigo, tu respuesta finalmente resolvió mi problema después de algunas horas de búsqueda en Internet. ¡Gracias! – mcy

6

Así es como lo estoy haciendo con MVVM. En mi modelo me he definido mi enumeración:

public enum VelocityUnitOfMeasure 
    { 
     [Description("Miles per Hour")] 
     MilesPerHour, 
     [Description("Kilometers per Hour")] 
     KilometersPerHour 
    } 

en mi modelo de vista expongo una propiedad que ofrece posibilidades de selección como cuerdas, así como una propiedad para obtener/ajustar el valor del modelo. Esto es útil si no queremos usar cada valor de enumeración en el tipo:

//UI Helper 
    public IEnumerable<string> VelocityUnitOfMeasureSelections 
    { 
     get 
     { 
      var units = new [] 
          { 
           VelocityUnitOfMeasure.MilesPerHour.Description(), 
           VelocityUnitOfMeasure.KilometersPerHour.Description() 
          }; 
      return units; 
     } 
    } 

    //VM property 
    public VelocityUnitOfMeasure UnitOfMeasure 
    { 
     get { return model.UnitOfMeasure; } 
     set { model.UnitOfMeasure = value; } 
    } 

Por otra parte, yo uso un EnumDescriptionCoverter genérica:

public class EnumDescriptionConverter : IValueConverter 
{ 
    //From Binding Source 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (!(value is Enum)) throw new ArgumentException("Value is not an Enum"); 
     return (value as Enum).Description(); 
    } 

    //From Binding Target 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (!(value is string)) throw new ArgumentException("Value is not a string"); 
     foreach(var item in Enum.GetValues(targetType)) 
     { 
      var asString = (item as Enum).Description(); 
      if (asString == (string) value) 
      { 
       return item; 
      } 
     } 
     throw new ArgumentException("Unable to match string to Enum description"); 
    } 
} 

Y, por último, con la opinión de que pueda hacer el siguiente:

<Window.Resources> 
    <ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" /> 
</Window.Resources> 
... 
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}" 
      ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" /> 
+0

es Enum.Description() y método de extensión? No puedo encontrar ese método en el tipo System.Enum. – mtijn

+0

.Description() es un método de extensión que obtiene el atributo de descripción. En retrospectiva, podría haber sido más apropiado usar el atributo DisplayName. –

+0

Pasé por alto el método de extensión en el cuerpo de la pregunta, probablemente era a lo que se refería, y DisplayName no se usa porque no se aplica a objetivos de campo enum (a menos que expanda el uso de atributos) – mtijn

0

lo he hecho así:

<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22" Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" > 
      <ComboBox.ItemsPanel> 
       <ItemsPanelTemplate> 
        <VirtualizingStackPanel/> 
       </ItemsPanelTemplate> 
      </ComboBox.ItemsPanel> 
     </ComboBox> 

de código que establece ItemSource:

CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))