Ligação de dados de uma propriedade enum a uma checkbox de combinação no WPF

Como exemplo, pegue o seguinte código:

public enum ExampleEnum { FooBar, BarFoo } public class ExampleClass : INotifyPropertyChanged { private ExampleEnum example; public ExampleEnum ExampleProperty { get { return example; } { /* set and notify */; } } } 

Eu quero um vincular a propriedade ExampleProperty a um ComboBox, para que ele mostra as opções “FooBar” e “BarFoo” e funciona no modo TwoWay. Idealmente, eu quero que minha definição de ComboBox seja algo como isto:

  

Atualmente eu tenho manipuladores para os events ComboBox.SelectionChanged e ExampleClass.PropertyChanged instalados na minha janela onde eu faço a binding manualmente.

Existe um caminho melhor ou de algum tipo canônico? Você usaria conversores normalmente e como você preencheria o ComboBox com os valores corretos? Eu nem quero começar com o i18n agora.

Editar

Então, uma pergunta foi respondida: Como faço para preencher o ComboBox com os valores corretos.

Recupere valores Enum como uma lista de strings por meio de um ObjectDataProvider do método estático Enum.GetValues:

        

Isso eu posso usar como um ItemsSource para meu ComboBox:

  

Você pode criar uma extensão de marcação personalizada.

Exemplo de uso:

 enum Status { [Description("Available.")] Available, [Description("Not here right now.")] Away, [Description("I don't have time right now.")] Busy } 
  

E a implementação …

 public class EnumerationExtension : MarkupExtension { private Type _enumType; public EnumerationExtension(Type enumType) { if (enumType == null) throw new ArgumentNullException("enumType"); EnumType = enumType; } public Type EnumType { get { return _enumType; } private set { if (_enumType == value) return; var enumType = Nullable.GetUnderlyingType(value) ?? value; if (enumType.IsEnum == false) throw new ArgumentException("Type must be an Enum."); _enumType = value; } } public override object ProvideValue(IServiceProvider serviceProvider) { var enumValues = Enum.GetValues(EnumType); return ( from object enumValue in enumValues select new EnumerationMember{ Value = enumValue, Description = GetDescription(enumValue) }).ToArray(); } private string GetDescription(object enumValue) { var descriptionAttribute = EnumType .GetField(enumValue.ToString()) .GetCustomAttributes(typeof (DescriptionAttribute), false) .FirstOrDefault() as DescriptionAttribute; return descriptionAttribute != null ? descriptionAttribute.Description : enumValue.ToString(); } public class EnumerationMember { public string Description { get; set; } public object Value { get; set; } } } 

No viewmodel você pode ter:

  public MyEnumType SelectedMyEnumType { get { return _selectedMyEnumType; } set { _selectedMyEnumType = value; OnPropertyChanged("SelectedMyEnumType"); } } public IEnumerable MyEnumTypeValues { get { return Enum.GetValues(typeof(MyEnumType)) .Cast(); } } 

Em XAML o ItemSource vincula a MyEnumTypeValues ​​e SelectedItem vincula a SelectedMyEnumType.

  

Eu prefiro não usar o nome de enum na interface do usuário. Eu prefiro usar valor diferente para usuário ( DisplayMemberPath ) e diferente para valor (enum neste caso) ( SelectedValuePath ). Esses dois valores podem ser compactados em KeyValuePair e armazenados no dictionary.

XAML

  

C #

 public Dictionary ExampleEnumsWithCaptions { get { return new Dictionary() // Fix. Each time new dict.? { {ExampleEnum.FooBar, "Foo Bar"}, {ExampleEnum.BarFoo, "Reversed Foo Bar"}, //{ExampleEnum.None, "Hidden in UI"}, }; } } private ExampleEnum example; public ExampleEnum ExampleProperty { get { return example; } set { /* set and notify */; } } 

EDIT: Compatível com o padrão MVVM.

Não sei se é possível apenas no XAML, mas tente o seguinte:

Dê um nome ao seu ComboBox para que você possa acessá-lo no codebehind: “typesComboBox1”

Agora tente o seguinte

 typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum)); 

Com base na resposta aceita, mas agora excluída fornecida por ageektrapped , criei uma versão simplificada sem alguns dos resources mais avançados. Todo o código está incluído aqui para permitir que você copie e cole e não seja bloqueado pelo link-rot.

Eu uso o System.ComponentModel.DescriptionAttribute que realmente é destinado a descrições de tempo de design. Se você não gosta de usar este atributo, você pode criar o seu próprio, mas acho que usar esse atributo realmente faz o trabalho. Se você não usar o atributo, o nome será padronizado para o nome do valor de enum no código.

 public enum ExampleEnum { [Description("Foo Bar")] FooBar, [Description("Bar Foo")] BarFoo } 

Aqui está a class usada como fonte dos itens:

 public class EnumItemsSource : Collection, IValueConverter { Type type; IDictionary valueToNameMap; IDictionary nameToValueMap; public Type Type { get { return this.type; } set { if (!value.IsEnum) throw new ArgumentException("Type is not an enum.", "value"); this.type = value; Initialize(); } } public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) { return this.valueToNameMap[value]; } public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) { return this.nameToValueMap[value]; } void Initialize() { this.valueToNameMap = this.type .GetFields(BindingFlags.Static | BindingFlags.Public) .ToDictionary(fi => fi.GetValue(null), GetDescription); this.nameToValueMap = this.valueToNameMap .ToDictionary(kvp => kvp.Value, kvp => kvp.Key); Clear(); foreach (String name in this.nameToValueMap.Keys) Add(name); } static Object GetDescription(FieldInfo fieldInfo) { var descriptionAttribute = (DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)); return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name; } } 

Você pode usá-lo em XAML assim:

     

Use ObjectDataProvider:

      

e, em seguida, ligar ao recurso estático:

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

Encontre esta solução neste blog

você pode considerar algo assim:

  1. defina um estilo para textblock ou qualquer outro controle que você queira usar para exibir seu enum:

        
  2. defina seu estilo para ComboBoxItem

       
  3. adicione uma checkbox de combinação e carregue-a com seus valores de enum:

         Value1    

Se o seu enum for grande, você pode fazer o mesmo no código, poupando bastante digitação. Eu gosto dessa abordagem, uma vez que facilita a localização – você define todos os modelos uma vez e, em seguida, atualiza apenas os arquivos de resources da cadeia de caracteres.

Aqui está uma solução genérica usando um método auxiliar. Isso também pode manipular um enum de qualquer tipo subjacente (byte, sbyte, uint, long, etc.)

Método Auxiliar:

 static IEnumerable GetEnum() { var type = typeof(T); var names = Enum.GetNames(type); var values = Enum.GetValues(type); var pairs = Enumerable.Range(0, names.Length) .Select(i => new { Name = names.GetValue(i) , Value = values.GetValue(i) }) .OrderBy(pair => pair.Name); return pairs; }//method 

Ver modelo:

 public IEnumerable EnumSearchTypes { get { return GetEnum(); } }//property 

Caixa combo:

  

Minha maneira favorita de fazer isso é com um ValueConverter para que o ItemsSource e o SelectedValue sejam vinculados à mesma propriedade. Isso não requer propriedades adicionais para manter seu ViewModel agradável e limpo.

  

E a definição do conversor:

 public static class EnumHelper { public static string Description(this Enum e) { return (e.GetType() .GetField(e.ToString()) .GetCustomAttributes(typeof(DescriptionAttribute), false) .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString(); } } [ValueConversion(typeof(Enum), typeof(IEnumerable))] public class EnumToCollectionConverter : MarkupExtension, IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return Enum.GetValues(value.GetType()) .Cast() .Select(e => new ValueDescription() { Value = e, Description = e.Description()}) .ToList(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } public override object ProvideValue(IServiceProvider serviceProvider) { return this; } } 

Este conversor funcionará com qualquer enum. ValueDescription é apenas uma class simples com uma propriedade Value e uma propriedade Description . Você poderia facilmente usar um Tuple com Item1 e Item2 , ou um KeyValuePair com Key e Value vez de Value e Description ou qualquer outra class de sua escolha, desde que ele possa conter um valor de enum e uma descrição de string desse valor enum.

Esta é uma resposta específica do DevExpress baseada na resposta mais votada por Gregor S. (atualmente tem 128 votos).

Isso significa que podemos manter o estilo consistente em todo o aplicativo:

insira a descrição da imagem aqui

Infelizmente, a resposta original não funciona com um ComboBoxEdit do DevExpress sem algumas modificações.

Primeiro, o XAML para o ComboBoxEdit :

  

Desnecessário dizer que você precisará apontar xamlExtensions no namespace que contém a class de extensão XAML (que é definida abaixo):

 xmlns:xamlExtensions="clr-namespace:XamlExtensions" 

E temos que apontar myEnum no namespace que contém o enum:

 xmlns:myEnum="clr-namespace:MyNamespace" 

Então, o enum:

 namespace MyNamespace { public enum EnumFilter { [Description("Free as a bird")] Free = 0, [Description("I'm Somewhat Busy")] SomewhatBusy = 1, [Description("I'm Really Busy")] ReallyBusy = 2 } } 

O problema com o XAML é que não podemos usar o SelectedItemValue , pois isso gera um erro, já que o configurador é inacessível (um pouco de descuido da sua parte, o DevExpress ). Então nós temos que modificar nosso ViewModel para obter o valor diretamente do object:

 private EnumFilter _filterSelected = EnumFilter.All; public object FilterSelected { get { return (EnumFilter)_filterSelected; } set { var x = (XamlExtensionEnumDropdown.EnumerationMember)value; if (x != null) { _filterSelected = (EnumFilter)x.Value; } OnPropertyChanged("FilterSelected"); } } 

Para completar, aqui está a extensão XAML da resposta original (ligeiramente renomeada):

 namespace XamlExtensions { ///  /// Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the /// dropdown box by using the [Description] attribute on the enum values. ///  public class XamlExtensionEnumDropdown : MarkupExtension { private Type _enumType; public XamlExtensionEnumDropdown(Type enumType) { if (enumType == null) { throw new ArgumentNullException("enumType"); } EnumType = enumType; } public Type EnumType { get { return _enumType; } private set { if (_enumType == value) { return; } var enumType = Nullable.GetUnderlyingType(value) ?? value; if (enumType.IsEnum == false) { throw new ArgumentException("Type must be an Enum."); } _enumType = value; } } public override object ProvideValue(IServiceProvider serviceProvider) { var enumValues = Enum.GetValues(EnumType); return ( from object enumValue in enumValues select new EnumerationMember { Value = enumValue, Description = GetDescription(enumValue) }).ToArray(); } private string GetDescription(object enumValue) { var descriptionAttribute = EnumType .GetField(enumValue.ToString()) .GetCustomAttributes(typeof (DescriptionAttribute), false) .FirstOrDefault() as DescriptionAttribute; return descriptionAttribute != null ? descriptionAttribute.Description : enumValue.ToString(); } #region Nested type: EnumerationMember public class EnumerationMember { public string Description { get; set; } public object Value { get; set; } } #endregion } } 

Isenção de responsabilidade: não tenho afiliação com o DevExpress. Telerik também é uma ótima biblioteca.

Se você estiver usando um MVVM, com base na resposta @rudigrobler, você pode fazer o seguinte:

Adicione a seguinte propriedade à class ViewModel

 public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum)); 

Em seguida, no XAML faça o seguinte:

  

Tente usar

  

Eu criei um projeto CodePlex de código aberto que faz isso. Você pode baixar o pacote NuGet aqui .