silverlight data-binding xaml enums

¡Unir ComboBoxes a enums... en Silverlight!



data-binding xaml (4)

¡Agh, hablé demasiado pronto! Hay una solución perfectamente buena , al menos en Silverlight 3. (Puede que solo esté en 3, ya que este hilo indica que se corrigió un error relacionado con esto en Silverlight 3).

Básicamente, necesita un único convertidor para la propiedad ItemsSource , pero puede ser completamente genérico sin utilizar ninguno de los métodos prohibidos, siempre que le pase el nombre de una propiedad cuyo tipo es MyEnum . Y la vinculación de datos con SelectedItem es completamente indolora; ¡no necesita convertidor! Bueno, al menos es el tiempo que no desee cadenas personalizadas para cada valor enum a través, por ejemplo, de la DescriptionAttribute , hmm ... probablemente necesite otro convertidor para esa; Espero poder hacerlo genérico.

Actualización: ¡ Hice un convertidor y funciona! Debo vincularme a SelectedIndex ahora, por desgracia, pero está bien. Usa estos tipos:

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 este tipo de enlace XAML:

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

Y este tipo de declaración de recursos 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>

Cualquier comentario sería apreciado, ya que soy algo así como XAML / Silverlight / WPF / etc. novato Por ejemplo, ¿ EnumToIntConverter.ConvertBack será lento, por lo que debería considerar usar un caché?

Entonces, la web y StackOverflow tienen muchas respuestas agradables sobre cómo vincular un cuadro combinado a una propiedad enum en WPF. Pero a Silverlight le faltan todas las características que hacen esto posible :(. Por ejemplo:

  1. No puede utilizar un EnumDisplayer genérico EnumDisplayer - IValueConverter que acepta un parámetro de tipo, ya que Silverlight no es compatible con x:Type .
  2. No puede usar ObjectDataProvider , como en este enfoque , ya que no existe en Silverlight.
  3. No puede usar una extensión de marcado personalizado como en los comentarios en el enlace desde el n.º 2, ya que las extensiones de marcado no existen en Silverlight.
  4. No se puede hacer una versión del n. ° 1 usando genéricos en lugar de propiedades de tipo del objeto, ya que los genéricos no son compatibles con XAML (y los ataques para que funcionen dependen de las extensiones de marcado, que no son compatibles con Silverlight).

¡Gran fracaso!

Como yo lo veo, la única manera de hacer que esto funcione es a cualquiera

  1. Haga trampa y agréguese a una propiedad de cadena en mi ViewModel, cuyo setter / getter realiza la conversión, cargando valores en el ComboBox utilizando el código subyacente en la Vista.
  2. Cree un IValueConverter personalizado para cada enumeración a la que desee enlazar.

¿Hay alguna alternativa que sea más genérica, es decir, no implique escribir el mismo código una y otra vez por cada enumeración que desee? Supongo que podría hacer la solución n. ° 2 usando una clase genérica que acepte la enumeración como un parámetro de tipo, y luego crear nuevas clases para cada enumeración que desee que sean simplemente

class MyEnumConverter : GenericEnumConverter<MyEnum> {}

¿Cuáles son sus pensamientos, muchachos?


Encuentro que una simple encapsulación de datos enum es mucho más fácil de usar.

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}" />

Y esto eliminará por completo la necesidad de un convertidor ... :)


Esta es la misma configuración para una aplicación universal para Windows 8.1 / Windows Phone, los principales cambios son:

  • Missing DescriptionAttribute en el framework (o al menos no puedo encontrarlo)
  • Diferencias en cómo funciona la reflexión (usando campos TypeInfo.Declared)

Parece que el orden de XAML también es importante, tuve que poner ItemsSource antes de SelectedIndex, de lo contrario no llamó al enlace de ItemsSource, por ejemplo

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

Código debajo

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