¿Cómo vincular una enumeración a un control de cuadro combinado en WPF?

182

Estoy tratando de encontrar un ejemplo simple donde las enumeraciones se muestran como están. Todos los ejemplos que he visto intentan agregar cadenas de visualización bonitas, pero no quiero esa complejidad.

Básicamente, tengo una clase que contiene todas las propiedades que enlazo, primero configurando el DataContext a esta clase y luego especificando el enlace como este en el archivo xaml:

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

Pero esto no muestra los valores de enumeración en los ComboBoxelementos as.

Joan Venge
fuente
9
Esto es lo que está buscando: ObjectDataProvider de WPF: enlace de Enum a ComboBox También puede descargar el ejemplo de código fuente completo desde allí.
La mejor respuesta en mi opinión está en: stackoverflow.com/questions/58743/…
gimpy

Respuestas:

307

Puede hacerlo desde el código colocando el siguiente código en el Loadedcontrolador de eventos de Windows , por ejemplo:

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

Si necesita enlazarlo en XAML, debe usarlo ObjectDataProviderpara crear un objeto disponible como fuente de enlace:

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

Llama la atención sobre el siguiente código:

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

Guía cómo mapear el espacio de nombres y el ensamblaje que puedes leer en MSDN .

Kyrylo M
fuente
1
Ejemplo probado del primer enlace, funciona bien. Ver código agregado y comentario en mi respuesta.
Kyrylo M
1
Encontró su problema en los foros de MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Intenta limpiar y reconstruir el proyecto. Probablemente debería preguntar por ese problema aquí en otra pregunta. Esto es lo único que puedo aconsejar ... De todos modos, el ejemplo mostrado es correcto.
Kyrylo M
1
Gracias, eso es extraño, pero he visto cosas similares con la locura de wpf. Lo haré y te lo haré saber. Por cierto, este es el mismo problema descrito aquí: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge
2
Necesitas agregarle referencias y agregar xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"XAML para usarlo. Aquí hay una guía: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M
44
Puede usar herramientas como ReSharper. Analiza todos los ensamblados a los que se hace referencia y ofrece sugerencias sobre lo que debe incluir. No es necesario escribir, solo seleccione entre las opciones.
Kyrylo M
117

Me gusta que todos los objetos que estoy vinculando se definan en mi ViewModel, así que trato de evitar usarlos <ObjectDataProvider>en el xaml cuando sea posible.

Mi solución no utiliza datos definidos en la Vista ni código subyacente. Solo un DataBinding, un ValueConverter reutilizable, un método para obtener una colección de descripciones para cualquier tipo de Enum y una única propiedad en ViewModel para enlazar.

Cuando quiero vincular un Enuma un ComboBoxtexto que quiero mostrar, nunca coincide con los valores de Enum, entonces uso el [Description()]atributo para darle el texto que realmente quiero ver en el ComboBox. Si tuviera una enumeración de días de la semana, se vería así:

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

Primero creé la clase auxiliar con un par de métodos para lidiar con las enumeraciones. Un método obtiene una descripción de un valor específico, el otro método obtiene todos los valores y sus descripciones para 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();
  }
}

A continuación, creamos un ValueConverter. Heredar de MarkupExtensionhace que sea más fácil de usar en XAML, por lo que no tenemos que declararlo como un recurso.

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

Mi ViewModelsólo tiene 1 propiedad que mi Viewpuede unirse a tanto para el SelectedValuey ItemsSourcedel cuadro combinado:

private DayOfWeek dayOfWeek;

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

Y finalmente para enlazar la ComboBoxvista (usando el ValueConverteren el ItemsSourceenlace) ...

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

Para implementar esta solución, solo necesita copiar mi EnumHelperclase y mi EnumToCollectionConverterclase. Trabajarán con cualquier enumeración. Además, no lo incluí aquí, pero la ValueDescriptionclase es solo una clase simple con 2 propiedades de objeto público, una llamada Value, una llamada Description. Puede crearlo usted mismo o puede cambiar el código para usar un Tuple<object, object>oKeyValuePair<object, object>

Mella
fuente
9
Para que esto funcione, tuve que crear una ValueDescriptionclase que tiene propiedades públicas para ValueyDescription
Perchik
44
Sí, también puede cambiar este código para usar Tuple<T1, T2>ao o en KeyValuePair<TKey, TValue>lugar de la ValueDescriptionclase y luego no tendría que crear el suyo.
Nick
Necesitaba implementar OnPropertyChanged (o el equivalente) para ambas propiedades ViewModel, no solo SelectedClass.
Se
No debería necesitar implementar OnPropertyChanged para la propiedad que devuelve la lista. La lista se genera a partir de los valores en una enumeración. Nunca cambiará durante el tiempo de ejecución, y cuando nunca cambia, nunca necesita notificar a nadie que ha cambiado. Además, con la versión actualizada, la propiedad de la lista ni siquiera es necesaria.
Nick
¿Cómo es ItemSource y SelectedValue del cuadro combinado la misma propiedad? ¿No es necesario que ItemsSource sea una lista? Oh, ya veo, es porque EnumHelper hace una lista de objetos. Esto en realidad hace que mi ViewModel sea más simple ya que no tengo que mantener una lista separada de objetos para llenar el ItemSource.
Stealth Rabbi
46

Usé otra solución usando MarkupExtension.

  1. Hice una clase que proporciona elementos de origen:

    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. Eso es casi todo ... Ahora úsalo en XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Cambie 'enumeraciones: Estados' a su enumeración

tom.maruska
fuente
1
@Nick: La respuesta aceptada también hace referencia a enum (o modelo como dijiste) en xaml. Su solución es crear 2 propiedades y un campo de respaldo en el modelo de vista, que no me gustó (regla DRY). Y, por supuesto, no tiene que usarlo e.ToString()para mostrar el nombre. Puede utilizar su propio traductor, analizador de atributos de descripción, lo que sea.
tom.maruska
2
@ tom.maruska No estoy tratando de entrar en mi respuesta frente a su respuesta, pero desde que lo mencionó, tener 2 propiedades no viola la regla DRY cuando son 2 propiedades distintas que tienen diferentes propósitos. Y su respuesta también requeriría agregar una propiedad (incluso llamó a esta propiedad usted mismo {Binding Path=WhereEverYouWant}) y si desea que sea compatible con el enlace bidireccional, también tendrá un campo de respaldo. Por lo tanto, no está reemplazando 2 propiedades y 1 campo de respaldo al hacer esto, solo está reemplazando 1 propiedad de solo lectura de una sola línea.
Nick
@ Nick Sí, tienes razón sobre ese campo de propiedad y respaldo :)
tom.maruska
25

Utilice ObjectDataProvider:

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

y luego enlazar al recurso estático:

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

basado en este artículo

druss
fuente
44
Solución perfectamente simple. Espacio de nombres para el sistema como en la respuesta de kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite
Funciona bien en proyectos WPF de Visual Studio 2017.
Sorush
11

La respuesta de Nick realmente me ayudó, pero me di cuenta de que podría modificarse ligeramente, para evitar una clase adicional, ValueDescription. Recordé que ya existe una clase KeyValuePair en el marco, por lo que se puede usar en su lugar.

El código cambia solo ligeramente:

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

y finalmente el XAML:

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

Espero que esto sea útil para otros.

Roger
fuente
Mi primera implementación usó un KeyValuePairpero al final decidí usar a KeyValuePairpara representar algo que no es un par clave-valor solo para evitar escribir una clase trivialmente simple no tenía mucho sentido. La ValueDescriptionclase es de solo 5 líneas, y 2 de ellas son justas {y}
Nick
8

Deberá crear una matriz de valores en la enumeración, que se puede crear llamando a System.Enum.GetValues ​​() , pasándole elType la enumeración de la que desea los elementos.

Si especifica esto para la ItemsSourcepropiedad, debe rellenarse con todos los valores de la enumeración. Es posible que desee unirse SelectedItema EffectStyle(suponiendo que es una propiedad de la misma enumeración, y contiene el valor actual).

Andy
fuente
Gracias, ¿puedes mostrar la primera parte en el código por favor? ¿No estoy seguro de dónde almacenar los valores de enumeración como matriz? La propiedad enum se encuentra en otra clase. ¿Puedo hacer este paso GetValues ​​dentro de xaml?
Joan Venge
4

Todas las publicaciones anteriores han perdido un simple truco. Es posible desde el enlace de SelectedValue descubrir cómo llenar los artículos de origen AUTOMÁTICAMENTE para que su marcado XAML sea justo.

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

Por ejemplo, en mi ViewModel tengo

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged es mi gancho INPC. El tuyo puede diferir.

La implementación de EnumComboBox es la siguiente, pero primero necesitaré un poco de ayuda para obtener mis cadenas y valores de enumeración

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

y la clase principal (tenga en cuenta que estoy usando ReactiveUI para enganchar cambios de propiedad a través de 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";

        }
    }
}

También debe establecer el estilo correctamente en Generic.XAML o su caja no mostrará nada y se arrancará el cabello.

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

y eso es eso. Obviamente, esto podría extenderse para admitir i18n, pero alargaría la publicación.

bradgonesurfing
fuente
3

Las aplicaciones universales parecen funcionar de manera un poco diferente; no tiene todo el poder de XAML con todas las funciones. Lo que funcionó para mí es:

  1. Creé una lista de los valores de enumeración como las enumeraciones (no convertidas en cadenas o enteros) y vinculé el ComboBox ItemsSource a eso
  2. Entonces podría vincular el elemento ComboBox ItemSelected a mi propiedad pública cuyo tipo es la enumeración en cuestión

Solo por diversión, preparé una pequeña clase con plantillas para ayudar con esto y la publiqué en las páginas de muestras de MSDN . Los bits adicionales me permiten anular opcionalmente los nombres de las enumeraciones y ocultar algunas de las enumeraciones. Mi código se parece mucho al de Nick (arriba), que desearía haber visto antes.

Ejecutando la muestra;  incluye múltiples enlaces de dos vías a la enumeración

PESMITH_MSFT
fuente
3

Hay muchas respuestas excelentes a esta pregunta y presento humildemente la mía. Encuentro que el mío es algo más simple y más elegante. Solo requiere un convertidor de valor.

Dada una enumeración ...

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
}

y un convertidor de valor ...

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

recursos ...

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

Declaración XAML ...

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

Ver modelo ...

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

Cuadro combinado resultante ...

ComboBox obligado a enumerar

AQuirky
fuente
Para mí, esta es la mejor solución a la pregunta: simple, fácil de entender, fácil de implementar.
Informagic
El problema con esta solución es que no es localizable.
Robin Davies
@RobinDavies puedes localizarlo. Requiere un atributo de descripción personalizado del cual he creado algunos. Vea esta pregunta SO para algunas ideas: 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));
    }
}

Debe extender la respuesta de Rogers y Greg con este tipo de convertidor de valor Enum, si está vinculando directamente a las propiedades del modelo de objeto enum.

Ruberoide
fuente
1

Si está vinculado a una propiedad de enumeración real en su ViewModel, no a una representación int de una enumeración, las cosas se ponen difíciles. Descubrí que es necesario vincular a la representación de cadena, NO al valor int como se espera en todos los ejemplos anteriores.

Puede saber si este es el caso vinculando un cuadro de texto simple a la propiedad que desea vincular en su ViewModel. Si muestra texto, enlace a la cadena. Si muestra un número, vincúlelo al valor. Tenga en cuenta que he usado Display dos veces, lo que normalmente sería un error, pero es la única forma en que funciona.

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

Greg

Greg Gum
fuente
Esta respuesta parece incompleta: * ¿Qué es / core /?
trapicki
1

Me gustó la respuesta de tom.maruska , pero necesitaba admitir cualquier tipo de enumeración que mi plantilla pudiera encontrar en tiempo de ejecución. Para eso, tuve que usar un enlace para especificar el tipo de la extensión de marcado. Pude trabajar en esta respuesta de nicolay.anykienko para llegar a una extensión de marcado muy flexible que funcionaría en cualquier caso que se me ocurra. Se consume así:

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

La fuente de la extensión de marcado combinada mencionada anteriormente:

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("_", " ");
    }
}
Hamish
fuente
1

Explicación simple y clara: 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>
jlo-gmail
fuente
0

Utilizando ReactiveUI, he creado la siguiente solución alternativa. No es una solución elegante todo en uno, pero creo que al menos es legible.

En mi caso, vincular una lista enuma un control es un caso raro, por lo que no necesito escalar la solución a través de la base de código. Sin embargo, el código puede hacerse más genérico cambiando EffectStyleLookup.Itema un Object. Lo probé con mi código, no son necesarias otras modificaciones. Lo que significa que la clase auxiliar podría aplicarse a cualquier enumlista. Aunque eso reduciría su legibilidad -ReactiveList<EnumLookupHelper> no tiene un gran sonido.

Usando la siguiente clase auxiliar:

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

En ViewModel, convierta la lista de enumeraciones y exponga como una propiedad:

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

En el ComboBox, utilice la SelectedValuePathpropiedad, para vincular al enumvalor original :

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

En la Vista, esto nos permite vincular el original enumal SelectedEffectStyleViewModel, pero mostrar el ToString()valor en 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) );
});
Mitkins
fuente
Creo que su ViewModel tiene un error. 1) ¿No debería ser una lista reactiva de EffectStyleLookup ?, 2) Primero debe hacer una lista reactiva vacía <T> (). Luego agrega los artículos. Finalmente: ReactiveList <T> ahora está en desuso (pero aún funciona). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (lista); Gracias por tomarte el tiempo de mostrar esto.
usuario1040323
0

Estoy agregando mi comentario (en VB, lamentablemente, pero el concepto se puede replicar fácilmente a C # en un santiamén), porque solo tuve que hacer referencia a esto y no me gustaron las respuestas, ya que eran demasiado complejas. No debería tener que ser tan difícil.

Entonces se me ocurrió una manera más fácil. Vincula los enumeradores a un diccionario. Vincula ese diccionario al cuadro combinado.

Mi cuadro combinado:

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

Mi código subyacente. Con suerte, esto ayuda a alguien más.

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
Laki Politis
fuente
La respuesta de Kyrylo es mucho más simple que la suya: no entiendo lo que es complicado. El suyo requiere cero conversión en el código.
Johnathon Sullinger
No quería poner toda mi lógica en manos de XAML. Prefiero hacer mi lógica a mi manera (no siempre la mejor manera), pero me permite entender dónde y por qué algo no está yendo de acuerdo al plan. El suyo es menos complicado, pero depende de XAML / WPF para hacer la lógica. Simplemente no soy fanático de eso. 10.000 formas de pelar un gato, ¿sabes?
Laki Politis
Lo suficientemente justo. Personalmente, prefiero usar las funciones ya construidas, listas para usar, para mí, pero esa es solo mi preferencia;) ¡Para cada una de ellas!
Johnathon Sullinger
¡Sí señor! Entiendo completamente. Me he visto obligado al desarrollo de software proveniente del desarrollo web. No he estado tan actualizado en WPF y tuve que aprender mucho a medida que avanzaba. Todavía no entiendo todas las complejidades de los controles WPF / XAML, por lo que he encontrado más problemas que soluciones en cómo esperaría que las cosas funcionen. Pero aprecio esta conversación. Me ha hecho investigar un poco más.
Laki Politis
0

La solución de Nick se puede simplificar más, sin nada lujoso, solo necesitaría un solo convertidor:

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

Luego, use esto donde quiera que aparezca su cuadro combinado:

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

No recomendaría implementar esto tal como está, pero espero que esto pueda inspirar una buena solución.

Digamos que tu enumeración es Foo. Entonces puedes hacer algo como esto.

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

Luego, en el Window.Loadmétodo, puede cargar todas las enumeraciones en una ObservableCollection<FooViewModel>que puede establecer como el DataContext del cuadro combinado.

Shaamil Ahmed
fuente
0

Lo mantuve simple. Creé una lista de elementos con los valores de enumeración en mi 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();
    }
}

En mi código xaml solo necesito esto:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Tsjakka
fuente