Eu gosto de todos os objetos que estou vinculando a serem definidos no meu ViewModel
, então tento evitar o uso <ObjectDataProvider>
no xaml quando possível.
Minha solução não usa dados definidos no modo de exibição e nenhum code-behind. Apenas um DataBinding, um ValueConverter reutilizável, um método para obter uma coleção de descrições para qualquer tipo de Enum e uma única propriedade no ViewModel à qual se vincular.
Quando eu quero vincular um Enum
ao ComboBox
texto que eu quero exibir nunca corresponde aos valores do Enum
, então eu uso o [Description()]
atributo para fornecer o texto que eu realmente quero ver no ComboBox
. Se eu tivesse um enum de dias da semana, seria algo como isto:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
Primeiro, criei a classe helper com alguns métodos para lidar com enumerações. Um método obtém uma descrição para um valor específico, o outro método obtém todos os valores e suas descrições para um 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();
}
}
Em seguida, criamos um ValueConverter
. A herança de MarkupExtension
facilita o uso no XAML, para que não seja necessário declará-lo como um 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;
}
}
Minha ViewModel
única precisa de 1 propriedade que meu View
pode vincular para o SelectedValue
e ItemsSource
da caixa de combinação:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
E, finalmente, para vincular a ComboBox
exibição (usando o ValueConverter
na ItemsSource
vinculação) ...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay}" />
Para implementar esta solução, você só precisa copiar minha EnumHelper
classe e EnumToCollectionConverter
classe. Eles vão trabalhar com qualquer enumeração. Além disso, não a incluí aqui, mas a ValueDescription
classe é apenas uma classe simples com 2 propriedades de objetos públicos, uma chamada Value
e outra chamada Description
. Você pode criar isso sozinho ou alterar o código para usar um Tuple<object, object>
ouKeyValuePair<object, object>