Como vincular um enum a um controle de checkbox de combinação no WPF?

Eu estou tentando encontrar um exemplo simples onde as enums são mostradas como estão. Todos os exemplos que vi tentam adicionar strings de exibição interessantes, mas não quero essa complexidade.

Basicamente eu tenho uma class que contém todas as propriedades que eu vinculo, primeiro definindo o DataContext para esta class e, em seguida, especificando a binding como esta no arquivo xaml:

 

Mas isso não mostra os valores de enum na ComboBox como itens.

Você pode fazer isso a partir do código, colocando o seguinte código no manipulador de events Window Loaded , por exemplo:

 yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast(); 

Se você precisar vinculá-lo em XAML, será necessário usar o ObjectDataProvider para criar o object disponível como fonte de binding:

             

Chame a atenção no próximo código:

 xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:StyleAlias="clr-namespace:Motion.VideoEffects" 

Guia como mapear namespace e assembly que você pode ler no MSDN .

Eu gosto de todos os objects que eu estou ligando para ser definido no meu ViewModel , então eu tento evitar o uso de no xaml quando possível.

Minha solução não usa dados definidos na Visualização e nenhum código por trás. Apenas um DataBinding, um ValueConverter reutilizável, um método para obter uma coleção de descrições para qualquer tipo Enum e uma única propriedade no ViewModel para vincular.

Quando eu quero vincular um Enum a um ComboBox o texto que eu quero exibir nunca coincide com os valores do Enum , então eu uso o atributo [Description()] para dar a ele o texto que eu realmente quero ver no ComboBox . Se eu tivesse um enum de classs de personagem em um jogo, seria algo parecido com isto:

 public enum PlayerClass { // add an optional blank value for default/no selection [Description("")] NOT_SET = 0, [Description("Shadow Knight")] SHADOW_KNIGHT, ... } 

Primeiro eu criei a class auxiliar com alguns methods para lidar com enums. 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 underscolors 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 GetAllValuesAndDescriptions(Type t) { if (!t.IsEnum) throw new ArgumentException($"{nameof(t)} must be an enum type"); return Enum.GetValues(t).Cast().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList(); } } 

Em seguida, criamos um ValueConverter . Herdar de MarkupExtension facilita o uso em XAML, portanto, não precisamos declará-lo como um recurso.

 [ValueConversion(typeof(Enum), typeof(IEnumerable))] 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; } } 

Meu ViewModel só precisa de 1 propriedade que meu View possa vincular para o SelectedValue e ItemsSource do combobox:

 private PlayerClass playerClass; public PlayerClass SelectedClass { get { return playerClass; } set { if (playerClass != value) { playerClass = value; OnPropertyChanged(nameof(SelectedClass)); } } } 

E, finalmente, para ligar a visão ComboBox (usando o ValueConverter na binding ItemsSource ) …

  

Para implementar essa solução, você só precisa copiar minhas classs EnumToCollectionConverter e EnumToCollectionConverter . Eles vão trabalhar com qualquer enums. Além disso, eu não o incluí aqui, mas a class ValueDescription é apenas uma class simples com duas propriedades de object público, uma chamada Value , uma chamada Description . Você pode criar você mesmo ou alterar o código para usar um Tuple ou KeyValuePair

Eu usei outra solução usando o MarkupExtension.

  1. Eu fiz class que fornece fonte de itens:

     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() .Select(e => new { Value = (int)e, DisplayName = e.ToString() }); } } 
  2. Isso é quase tudo … Agora use em XAML:

       
  3. Alterar ‘enums: states’ para o seu enum

Use ObjectDataProvider:

      

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

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

A resposta de Nick realmente me ajudou, mas percebi que poderia ser um pouco modificado, para evitar uma aula extra, ValueDescription. Eu lembrei que existe uma class KeyValuePair já no framework, então isso pode ser usado no lugar.

O código muda apenas ligeiramente:

 public static IEnumerable> GetAllValuesAndDescriptions() where TEnum : struct, IConvertible, IComparable, IFormattable { if (!typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an Enumeration type"); } return from e in Enum.GetValues(typeof(TEnum)).Cast() select new KeyValuePair(e.ToString(), e.Description()); } public IEnumerable> PlayerClassList { get { return EnumHelper.GetAllValuesAndDescriptions(); } } 

e finalmente o XAML:

  

Espero que isso seja útil para os outros.

Você precisará criar uma matriz dos valores no enum, que pode ser criado chamando System.Enum.GetValues ​​() , passando o Type do enum do qual deseja os itens.

Se você especificar isso para a propriedade ItemsSource , ele deverá ser preenchido com todos os valores do enum. Você provavelmente deseja vincular SelectedItem para EffectStyle (supondo que é uma propriedade da mesma enum e contém o valor atual).

Todos os posts acima perderam um truque simples. É possível a partir da binding de SelectedValue para descobrir como preencher o ItemsSource AUTOMAGICALLY para que sua marcação XAML seja justa.

  

Por exemplo, no meu ViewModel eu tenho

 public enum FoolEnum { AAA, BBB, CCC, DDD }; FoolEnum _Fool; public FoolEnum Fool { get { return _Fool; } set { ValidateRaiseAndSetIfChanged(ref _Fool, value); } } 

ValidateRaiseAndSetIfChanged é o meu gancho INPC. Seu pode diferir.

A implementação do EnumComboBox é a seguinte, mas primeiro eu precisarei de um pequeno ajudante para obter minhas cadeias de enumeração e valores

  public static List> EnumToList(Type t) { return Enum .GetValues(t) .Cast() .Select(x=>Tuple.Create(x, x.ToString(), (int)x)) .ToList(); } 

e a class principal (Observe que estou usando ReactiveUI para ligar as alterações de propriedade via WhenAny)

 using ReactiveUI; using ReactiveUI.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Windows; using System.Windows.Documents; namespace My.Controls { public class EnumComboBox : System.Windows.Controls.ComboBox { static EnumComboBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox))); } protected override void OnInitialized( EventArgs e ) { base.OnInitialized(e); this.WhenAnyValue(p => p.SelectedValue) .Where(p => p != null) .Select(o => o.GetType()) .Where(t => t.IsEnum) .DistinctUntilChanged() .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(FillItems); } private void FillItems(Type enumType) { List> values = new List>(); foreach (var idx in EnumUtils.EnumToList(enumType)) { values.Add(new KeyValuePair(idx.Item1, idx.Item2)); } this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList(); UpdateLayout(); this.ItemsSource = values; this.DisplayMemberPath = "Value"; this.SelectedValuePath = "Key"; } } } 

Você também precisa definir o estilo corretamente em Generic.XAML ou sua checkbox não renderizará nada e você puxará seu cabelo para fora.

  

e é isso. Isso obviamente poderia ser estendido para suportar o i18n, mas tornaria o post mais longo.

 public class EnumItemsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!value.GetType().IsEnum) return false; var enumName = value.GetType(); var obj = Enum.Parse(enumName, value.ToString()); return System.Convert.ToInt32(obj); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return Enum.ToObject(targetType, System.Convert.ToInt32(value)); } } 

Você deve estender a resposta de Rogers e Greg com esse tipo de conversor de valores de Enum, se você estiver vinculado diretamente às propriedades do modelo de object de enum.

Aplicativos universais parecem funcionar de maneira um pouco diferente; ele não tem todo o poder do XAML completo. O que funcionou para mim é:

  1. Eu criei uma lista dos valores de enum como enums (não convertidos em strings ou em inteiros) e liguei o ComboBox ItemsSource àquele
  2. Então eu poderia vincular o ComboBox ItemSelected à minha propriedade pública cujo tipo é o enum em questão

Apenas por diversão eu peguei um pouco de class de templates para ajudar com isso e publiquei nas páginas MSDN Samples . Os bits extras permitem-me, opcionalmente, replace os nomes dos enums e deixar-me ocultar algumas das enums. Meu código parece horrível como o de Nick (acima), que eu gostaria de ter visto antes.

Executando a amostra; inclui várias ligações entre duas partes do enum

Se você estiver ligando a uma propriedade enum real em seu ViewModel, não uma representação int de um enum, as coisas ficam complicadas. Eu achei que é necessário vincular a representação de seqüência de caracteres, não o valor int como é esperado em todos os exemplos acima.

Você pode dizer se este é o caso, ligando uma checkbox de texto simples para a propriedade que você deseja vincular em seu ViewModel. Se mostrar texto, ligue-se à string. Se mostrar um número, ligue-se ao valor. Note que usei o Display duas vezes, o que normalmente seria um erro, mas é a única maneira de funcionar.

  

Greg

Gostei da resposta de tom.maruska , mas precisava suportar qualquer tipo de enumeração que meu modelo pudesse encontrar em tempo de execução. Para isso, eu tive que usar uma binding para especificar o tipo para a extensão de marcação. Eu pude trabalhar nesta resposta do nicolay.anykienko para chegar a uma extensão de marcação muito flexível que funcionaria em qualquer caso eu posso pensar. É consumido assim:

  

A fonte da extensão de marcação purificada mencionada acima:

 class EnumToObjectArray : MarkupExtension { public BindingBase SourceEnum { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; DependencyObject targetObject; DependencyProperty targetProperty; if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty) { targetObject = (DependencyObject)target.TargetObject; targetProperty = (DependencyProperty)target.TargetProperty; } else { return this; } BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum); var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType(); if (type.BaseType != typeof(System.Enum)) return this; return Enum.GetValues(type) .Cast() .Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) }); } private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum) , typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); ///  /// Extension method which returns the string specified in the Description attribute, if any. Oherwise, name is returned. ///  /// The enum value. ///  public static string Description(Enum value) { var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); if (attrs.Any()) return (attrs.First() as DescriptionAttribute).Description; //Fallback return value.ToString().Replace("_", " "); } } 

Há muitas respostas excelentes para essa pergunta e eu humildemente submeto a minha. Eu acho que o meu é um pouco mais simples e elegante. Requer apenas um conversor de valor.

Dado um enum …

 public enum ImageFormat { [Description("Windows Bitmap")] BMP, [Description("Graphics Interchange Format")] GIF, [Description("Joint Photographic Experts Group Format")] JPG, [Description("Portable Network Graphics Format")] PNG, [Description("Tagged Image Format")] TIFF, [Description("Windows Media Photo Format")] WDP } 

e um conversor de valor …

 public class ImageFormatValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ImageFormat format) { return GetString(format); } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string s) { return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':'))); } return null; } public string[] Strings => GetStrings(); public static string GetString(ImageFormat format) { return format.ToString() + ": " + GetDescription(format); } public static string GetDescription(ImageFormat format) { return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute().Description; } public static string[] GetStrings() { List list = new List(); foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat))) { list.Add(GetString(format)); } return list.ToArray(); } } 

Recursos…

   

Declaração XAML …

   

Visualizar modelo …

  private ImageFormat _imageFormat = ImageFormat.JPG; public ImageFormat Format { get => _imageFormat; set { if (_imageFormat != value) { _imageFormat = value; OnPropertyChanged(); } } } 

Combobox resultante …

ComboBox ligado ao enum

Usando ReactiveUI , criei a seguinte solução alternativa. Não é uma solução all-in-one elegante, mas acho que, no mínimo, é legível.

No meu caso, vincular uma lista de enum a um controle é um caso raro, portanto, não preciso escalar a solução em toda a base de código. No entanto, o código pode ser mais genérico, alterando EffectStyleLookup.Item em um Object . Eu testei com o meu código, sem outras modificações são necessárias. O que significa que a única class auxiliar pode ser aplicada a qualquer lista enum . Embora isso reduza a legibilidade, o ReactiveList não é muito interessante.

Usando a seguinte class auxiliar:

 public class EffectStyleLookup { public EffectStyle Item { get; set; } public string Display { get; set; } } 

No ViewModel, converta a lista de enums e exponha-a como uma propriedade:

 public ViewModel : ReactiveObject { private ReactiveList _effectStyles; public ReactiveList EffectStyles { get { return _effectStyles; } set { this.RaiseAndSetIfChanged(ref _effectStyles, value); } } // See below for more on this private EffectStyle _selectedEffectStyle; public EffectStyle SelectedEffectStyle { get { return _selectedEffectStyle; } set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); } } public ViewModel() { // Convert a list of enums into a ReactiveList var list = (IList)Enum.GetValues(typeof(EffectStyle)) .Select( x => new EffectStyleLookup() { Item = x, Display = x.ToString() }); EffectStyles = new ReactiveList( list ); } } 

Na ComboBox , utilize a propriedade SelectedValuePath , para vincular ao valor de enum original:

  

Na View, isso nos permite vincular o enum original ao SelectedEffectStyle no ViewModel, mas exibir o valor ToString() no ComboBox :

 this.WhenActivated( d => { d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) ); d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) ); }); 

Estou adicionando o meu comentário (no VB, infelizmente, mas o conceito pode ser facilmente replicado para C # em um piscar de olhos), porque eu só tinha que fazer referência a isso e não gostou de nenhuma das respostas, pois eles eram muito complexos. Não deveria ser tão difícil.

Então eu criei um jeito mais fácil. Vincule os enumeradores a um dictionary. Vincule esse dictionary ao Combobox.

Minha combobox:

  

Meu code-behind. Espero que isso ajude alguém a sair.

 Dim tDict As New Dictionary(Of Integer, String) Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType)) For Each x As Helper.Enumerators.AllowedType In types Dim z = x.ToString() Dim y = CInt(x) tDict.Add(y, z) Next cmbRole.ClearValue(ItemsControl.ItemsSourceProperty) cmbRole.ItemsSource = tDict 

Explicação simples e clara: http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

 xmlns:local="clr-namespace:BindingEnums" xmlns:sys="clr-namespace:System;assembly=mscorlib"