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 Enum
a un ComboBox
texto 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 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;
}
}
Mi ViewModel
sólo tiene 1 propiedad que mi View
puede unirse a tanto para el SelectedValue
y ItemsSource
del 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 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}" />
Para implementar esta solución, solo necesita copiar mi EnumHelper
clase y mi EnumToCollectionConverter
clase. Trabajarán con cualquier enumeración. Además, no lo incluí aquí, pero la ValueDescription
clase 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>