¿Cómo vincular una enumeración a un control de cuadro combinado en WPF?
Estoy tratando de encontrar un ejemplo simple donde las enumeraciones se muestren tal como están. Todos los ejemplos que he visto intentan agregar cadenas de visualización atractivas, pero no quiero esa complejidad.
Básicamente, tengo una clase que contiene todas las propiedades que vinculo, estableciendo primero el DataContext en esta clase y luego especificando el enlace de esta manera en el archivo xaml:
<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>
Pero esto no muestra los valores de enumeración en los ComboBox
elementos as.
Puedes hacerlo desde el código colocando el siguiente código en el Loaded
controlador de eventos de Windows, por ejemplo:
yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();
Si necesita vincularlo en XAML, debe usarlo ObjectDataProvider
para 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"
Puede leer una guía sobre cómo asignar espacios de nombres y ensamblados en MSDN .
Me gusta que todos los objetos que estoy vinculando se definan en my ViewModel
, por lo que trato de evitar usarlos <ObjectDataProvider>
en 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 a la que vincularse.
Cuando quiero vincular un Enum
a a ComboBox
, el texto que quiero mostrar nunca coincide con los valores de Enum
, así que uso el [Description()]
atributo (from System.ComponentModel
) para darle el texto que realmente quiero ver en 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é una clase auxiliar con un par de métodos para manejar enumeraciones. Un método obtiene una descripción de un valor específico, el otro método obtiene todos los valores y sus descripciones de 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 MarkupExtension
hace 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;
}
}
Solo necesito ViewModel
1 propiedad a la que View
pueda vincularme tanto para el cuadro combinado SelectedValue
como ItemsSource
para el cuadro combinado:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
Y finalmente para vincular la ComboBox
vista (usando el ValueConverter
en el ItemsSource
enlace)...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Para implementar esta solución solo necesitas copiar mi EnumHelper
clase y EnumToCollectionConverter
mi clase. Trabajarán con cualquier enumeración. Además, no lo incluí aquí, pero la ValueDescription
clase es simplemente una clase simple con 2 propiedades de objeto público, una llamada Value
y otra llamada Description
. Puede crearlo usted mismo o puede cambiar el código para usar un Tuple<object, object>
oKeyValuePair<object, object>
Para los que querían ver la ValueDescription
clase:
public class ValueDescription
{
public object Value { get; set; }
public object Description { get; set; }
}
Utilicé otra solución usando MarkupExtension.
Hice una clase que proporciona la fuente de los elementos:
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() }); } }
Eso es casi todo... Ahora úsalo en XAML:
<ComboBox DisplayMemberPath="DisplayName" ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}" SelectedValue="{Binding Path=WhereEverYouWant}" SelectedValuePath="Value" />
Cambie 'enums:States' a su enumeración
Utilice el proveedor de datos de objeto:
<ObjectDataProvider x:Key="enumValues"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExampleEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
y luego vincularse al recurso estático:
ItemsSource="{Binding Source={StaticResource enumValues}}"
basado en este artículo