Como vincular um enum a um controle combobox no WPF?

182

Estou tentando encontrar um exemplo simples onde as enumerações são mostradas como estão. Todos os exemplos que eu vi tentam adicionar seqüências de exibição com boa aparência, mas não quero essa complexidade.

Basicamente, eu tenho uma classe que contém todas as propriedades que eu vinculo, definindo primeiro o DataContext para essa classe e especificando a ligação como esta no arquivo xaml:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

Mas isso não mostra os valores de enumeração nos ComboBoxitens as.

Joan Venge
fonte
9
Aqui está o que você está procurando: WPF ObjectDataProvider - Vinculando enum à ComboBox Você também pode fazer o download do exemplo completo do código-fonte a partir daí.
A melhor resposta na minha opinião está em: stackoverflow.com/questions/58743/...
gimpy

Respostas:

306

Você pode fazer isso a partir do código, colocando o seguinte código no Loadedmanipulador de eventos do Windows , por exemplo:

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

Se você precisar vinculá-lo ao XAML, precisará ObjectDataProvidercriar o objeto disponível como fonte de vinculação:

<Window x:Class="YourNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:StyleAlias="clr-namespace:Motion.VideoEffects">
    <Window.Resources>
        <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                            ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="StyleAlias:EffectStyle"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
                  SelectedItem="{Binding Path=CurrentEffectStyle}" />
    </Grid>
</Window>

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

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

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

Kyrylo M
fonte
1
Exemplo testado do primeiro link, funciona bem. Veja código adicionado e comentário na minha resposta.
26711 Kyrylo M
1
Encontre seu problema nos fóruns do MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Tente limpar e reconstruir o projeto. Provavelmente você deve pedir esse problema aqui em outra pergunta. Este é o único que posso aconselhar ... Enfim, o exemplo mostrado está correto.
Kyrylo M
1
Obrigado, isso é bizarro, mas já vi coisas semelhantes com a loucura do wpf. Vai fazer e que você saiba. Btw é este o mesmo problema descrito aqui: social.msdn.microsoft.com/Forums/en-US/wpf/thread/...
Joan Venge
2
Você precisa adicionar referência a ele e adicionar xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"XAML para usá-lo. Aqui está o guia: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M
4
Você pode usar ferramentas como ReSharper. Ele analisa todos os assemblies referenciados e dá sugestões do que precisa incluir. Não há necessidade de escrever - basta selecionar entre as opções.
Kyrylo M
117

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 Enumao ComboBoxtexto 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 MarkupExtensionfacilita 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 Viewpode vincular para o SelectedValuee ItemsSourceda 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 ComboBoxexibição (usando o ValueConverterna ItemsSourcevinculaçã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 EnumHelperclasse e EnumToCollectionConverterclasse. Eles vão trabalhar com qualquer enumeração. Além disso, não a incluí aqui, mas a ValueDescriptionclasse é apenas uma classe simples com 2 propriedades de objetos públicos, uma chamada Valuee outra chamada Description. Você pode criar isso sozinho ou alterar o código para usar um Tuple<object, object>ouKeyValuePair<object, object>

usuario
fonte
9
Para fazer este trabalho, tive que criar uma ValueDescriptionclasse que tem propriedades públicas para ValueeDescription
Perchik
4
Sim, você também pode alterar esse código para usar um Tuple<T1, T2>ou ou em KeyValuePair<TKey, TValue>vez da ValueDescriptionclasse e não precisará criar o seu próprio.
Nick
Eu precisava implementar OnPropertyChanged (ou equivalente) para as duas propriedades ViewModel, não apenas SelectedClass.
Will
Você não precisa implementar OnPropertyChanged para a propriedade que retorna a lista. A lista é gerada a partir dos valores em um Enum. Ele nunca será alterado durante o tempo de execução e, quando nunca for alterado, nunca precisará notificar ninguém que foi alterado. Além disso, com a versão atualizada, a propriedade list nem é necessária.
19416 Nick
Como a ItemSource e o SelectedValue da caixa de combinação são a mesma propriedade. O ItemsSource não precisa ser uma lista? Ah, entendo, é porque o EnumHelper faz uma lista de objetos. na verdade, isso simplifica meu ViewModel, já que não preciso manter uma lista separada de objetos para preencher o ItemSource.
Rabino furtivo
46

Eu usei outra solução usando o MarkupExtension.

  1. Eu fiz classe que fornece itens de origem:

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

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Alterar 'enums: States' para sua enum

tom.maruska
fonte
1
@ Nick: A resposta aceita é referenciar enum (ou modelo como você disse) no xaml também. Sua solução está criando duas propriedades e um campo de apoio no modelo de exibição, que eu não gostei (regra DRY). E, claro, você não precisa usar o e.ToString()nome para exibição. Você pode usar seu próprio tradutor, analisador de atributos de descrição, o que for.
tom.maruska
2
@ tom.maruska Não estou tentando entrar na minha resposta x sua resposta, mas desde que você a mencionou, ter 2 propriedades não viola a regra DRY quando são 2 propriedades distintas que servem a propósitos diferentes. E sua resposta também exigiria a adição de uma propriedade (você mesmo chamou essa propriedade {Binding Path=WhereEverYouWant}) e, se desejar que ela suporte a ligação bidirecional, também terá um campo de apoio para ela. Portanto, você não está substituindo duas propriedades e um campo de apoio ao fazer isso, mas apenas uma propriedade de linha única de leitura.
Nick
@ Nick Sim, você está certo sobre essa propriedade e backing campo :)
tom.maruska
24

Use ObjectDataProvider:

<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

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

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

com base neste artigo

druss
fonte
4
Solução perfeitamente simples. Namespace para o Sistema como na resposta de kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite
Funciona bem em projetos WPF do Visual Studio 2017.
Sorush 26/03/19
11

A resposta de Nick realmente me ajudou, mas eu percebi que poderia ser um pouco modificado, para evitar uma classe extra, ValueDescription. Lembrei-me de que já existe uma classe KeyValuePair na estrutura, portanto, isso pode ser usado.

O código muda apenas um pouco:

public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() 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<Enum>()
               select new KeyValuePair<string, string>(e.ToString(),  e.Description());
    }


public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
   get
   {
       return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
   }
}

e finalmente o XAML:

<ComboBox ItemSource="{Binding Path=PlayerClassList}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=SelectedClass}" />

Espero que isso seja útil para os outros.

Roger
fonte
Minha primeira implementação usou a, KeyValuePairmas no final decidi usar a KeyValuePairpara representar algo que não é um par de valores-chave, apenas para evitar escrever uma classe trivialmente simples não fazia muito sentido. A ValueDescriptionclasse tem apenas 5 linhas, e 2 delas são justas {e}
Nick
8

Você precisará criar uma matriz dos valores na enumeração, que pode ser criada chamando System.Enum.GetValues ​​() , passando Typea enumeração da qual você deseja os itens.

Se você especificar isso para a ItemsSourcepropriedade, ela deverá ser preenchida com todos os valores da enumeração. Você provavelmente deseja vincular SelectedItema EffectStyle(assumindo que seja uma propriedade da mesma enumeração e contenha o valor atual).

Andy
fonte
Obrigado, você pode mostrar a primeira parte do código, por favor? Não sei onde armazenar os valores de enumeração como matriz? A propriedade enum está localizada em outra classe. Posso fazer este passo GetValues ​​dentro do xaml?
Joan Venge 26/05
4

Todas as postagens acima perderam um truque simples. É possível, a partir da ligação de SelectedValue, descobrir como preencher o ItemsSource AUTOMAGICAMENTE, para que sua marcação XAML seja justa.

<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>

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 é meu gancho INPC. O seu pode ser diferente.

A implementação do EnumComboBox é a seguinte, mas primeiro precisarei de um pouco de ajuda para obter minhas seqüências e valores de enumeração

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

e a classe principal (note que estou usando o ReactiveUI para conectar 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<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();

            foreach (var idx in EnumUtils.EnumToList(enumType))
            {
                values.Add(new KeyValuePair<object, string>(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 no Generic.XAML ou a sua caixa não renderizará nada e você arrancará o cabelo.

<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>

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

bradgonesurfing
fonte
3

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

  1. Criei uma lista dos valores de enumeração como enumerações (não convertidas em seqüências de caracteres ou números inteiros) e vinculei o ComboBox ItemsSource a esse
  2. Então eu poderia vincular o ComboBox ItemSelected à minha propriedade pública cujo tipo é o enum em questão

Apenas por diversão, criei uma turma pequena para ajudá-lo e publiquei-a nas páginas de amostras do MSDN . Os bits extras permitem que eu substitua opcionalmente os nomes das enumerações e deixe-me ocultar algumas delas. Meu código se parece muito com o de Nick (acima), que eu gostaria de ter visto anteriormente.

Executando a amostra;  inclui várias ligações de duas vias ao enum

PESMITH_MSFT
fonte
3

Existem muitas respostas excelentes para essa pergunta e eu humildemente submeto a minha. Acho que o meu é um pouco mais simples e mais 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<DescriptionAttribute>().Description;

    }
    public static string[] GetStrings()
    {
        List<string> list = new List<string>();
        foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
        {
            list.Add(GetString(format));
        }

        return list.ToArray();
    }
}

Recursos...

    <local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>

Declaração XAML ...

    <ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
              SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>

Ver modelo ...

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

Caixa de combinação resultante ...

ComboBox vinculado a enum

AQuirky
fonte
Para mim, esta é a melhor solução para a pergunta: simples, fácil de entender, direta de implementar.
Informagic 17/01/19
O problema com esta solução é que ela não é localizável.
Robin Davies
@RobinDavies você pode localizá-lo. Requer um DescriptionAttribute personalizado, do qual criei alguns. Veja esta pergunta SO para algumas idéias: stackoverflow.com/questions/7398653/…
AQuirky
2
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 valor Enum, se estiver vinculando diretamente as propriedades do modelo de objeto enum.

Ruberoid
fonte
1

Se você está vinculando a uma propriedade enum real no seu ViewModel, não a uma representação int de uma enum, as coisas ficam complicadas. Achei que é necessário vincular à representação da string, NÃO o valor int, como é esperado em todos os exemplos acima.

Você pode saber se esse é o caso, vinculando uma caixa de texto simples à propriedade que você deseja vincular ao seu ViewModel. Se mostrar texto, vincule à string. Se ele mostrar um número, vincule ao valor. Observe que usei o Display duas vezes, o que normalmente seria um erro, mas é a única maneira de funcionar.

<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
                      DisplayMemberPath="Display"
                      SelectedValuePath="Display"
                      ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />

Greg

Greg Gum
fonte
Esta resposta parece incompleta: * O que é / core /?
trapicki
1

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

<ComboBox SelectedValue="{Binding MyEnumProperty}" 
          SelectedValuePath="Value"
          ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" 
          DisplayMemberPath="DisplayName" />

A fonte da extensão de marcação amassada 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<Enum>()
            .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));

    /// <summary>
    /// Extension method which returns the string specified in the Description attribute, if any.  Oherwise, name is returned.
    /// </summary>
    /// <param name="value">The enum value.</param>
    /// <returns></returns>
    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("_", " ");
    }
}
Hamish
fonte
1

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"

...

<Window.Resources>
    <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:Status"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

...

<Grid>
    <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
              ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
jlo-gmail
fonte
0

Usando ReactiveUI, eu criei a seguinte solução alternativa. Não é uma solução multifuncional elegante, mas acho que pelo menos é legível.

No meu caso, vincular uma lista de enuma um controle é um caso raro, portanto, não preciso escalar a solução na base de código. No entanto, o código pode ser tornado mais genérico mudando EffectStyleLookup.Itempara um Object. Eu testei com o meu código, nenhuma outra modificação é necessária. O que significa que a classe auxiliar pode ser aplicada a qualquer enumlista. Embora isso reduzisse sua legibilidade -ReactiveList<EnumLookupHelper> não tem um grande toque.

Usando a seguinte classe auxiliar:

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

No ViewModel, converta a lista de enumerações e exponha-a como uma propriedade:

public ViewModel : ReactiveObject
{
  private ReactiveList<EffectStyleLookup> _effectStyles;
  public ReactiveList<EffectStyleLookup> 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<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
      .Select( x => new EffectStyleLookup() { 
        Item = x, 
        Display = x.ToString()
      });

    EffectStyles = new ReactiveList<EffectStyle>( list );
  }
}

No ComboBox, utilize oSelectedValuePath propriedade, para vincular ao enumvalor original :

<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

Na visualização, isso nos permite vincular o original enumao SelectedEffectStyleno ViewModel, mas exibir o ToString()valor 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) );
});
Mitkins
fonte
Eu acho que seu ViewModel tem um erro. 1) Não deve ser uma ReactiveList de EffectStyleLookup ?, 2) Você deve criar uma ReactiveList vazia <T> () primeiro. Em seguida, adicione os itens. Finalmente: ReactiveList <T> agora está obsoleto (mas ainda funciona). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (lista); Obrigado por dedicar um tempo para mostrar isso.
user1040323
0

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

Então, eu vim com uma maneira mais fácil. Vincule os enumeradores a um dicionário. Vincule esse dicionário à caixa de combinação.

Minha caixa de combinação:

<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" 
    Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" 
    SelectedValuePath="Key" DisplayMemberPath="Value" />

Meu code-behind. Felizmente, isso ajuda alguém.

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
Laki Politis
fonte
A resposta de Kyrylo é muito mais simples que a sua - não entendo o que é complicado nisso? Sua requer zero conversão em código.
19416 Johnathon Sullinger
Eu não queria colocar toda a minha lógica nas mãos do XAML. Prefiro fazer minha lógica do meu jeito (nem sempre da melhor maneira), mas isso me permite entender onde e por que algo não está indo conforme o planejado. Ele é menos complicado, mas depende do XAML / WPF para fazer a lógica. Eu simplesmente não sou fã disso. 10.000 maneiras de esfolar um gato, sabia?
Laki Politis
Justo. Pessoalmente, prefiro usar recursos já criados, prontos para uso, para mim, mas essa é apenas a minha preferência;) Para cada um deles!
Johnathon Sullinger 23/11
Sim senhor! Eu entendo completamente. Fui forçado a desenvolver software vindo de desenvolvimento web. Não tenho estado tão atualizado sobre o WPF e tive que aprender muito ao longo do tempo. Ainda não entendo todos os meandros dos controles WPF / XAML e, portanto, tenho encontrado mais soluços do que soluções em como esperaria que as coisas funcionassem. Mas eu aprecio essa conversa. Isso me fez fazer mais algumas pesquisas.
Laki Politis
0

A solução de Nick pode ser mais simplificada, sem nada demais, você precisaria apenas de um único conversor:

[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var r = Enum.GetValues(value.GetType());
        return r;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

Você então usa isso onde quer que sua caixa de combinação apareça:

<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />
Jack
fonte
0

Eu não recomendaria implementar isso como está, mas espero que isso possa inspirar uma boa solução.

Digamos que seu enum é Foo. Então você pode fazer algo assim.

public class FooViewModel : ViewModel
{
    private int _fooValue;

    public int FooValue
    {
        get => _fooValue;
        set
        {
            _fooValue = value;
            OnPropertyChange();
            OnPropertyChange(nameof(Foo));
            OnPropertyChange(nameof(FooName));
        }
    }
    public Foo Foo 
    { 
        get => (Foo)FooValue; 
        set 
        { 
            _fooValue = (int)value;
            OnPropertyChange();
            OnPropertyChange(nameof(FooValue));
            OnPropertyChange(nameof(FooName));
        } 
    }
    public string FooName { get => Enum.GetName(typeof(Foo), Foo); }

    public FooViewModel(Foo foo)
    {
        Foo = foo;
    }
}

Em seguida, no Window.Loadmétodo, você pode carregar todas as enumerações para as ObservableCollection<FooViewModel>quais você pode definir como DataContext da caixa de combinação.

Shaamil Ahmed
fonte
0

Eu apenas mantive isso simples. Criei uma lista de itens com os valores de enumeração no meu ViewModel:

public enum InputsOutputsBoth
{
    Inputs,
    Outputs,
    Both
}

private IList<InputsOutputsBoth> _ioTypes = new List<InputsOutputsBoth>() 
{ 
    InputsOutputsBoth.Both, 
    InputsOutputsBoth.Inputs, 
    InputsOutputsBoth.Outputs 
};

public IEnumerable<InputsOutputsBoth> IoTypes
{
    get { return _ioTypes; }
    set { }
}

private InputsOutputsBoth _selectedIoType;

public InputsOutputsBoth SelectedIoType
{
    get { return _selectedIoType; }
    set
    {
        _selectedIoType = value;
        OnPropertyChanged("SelectedIoType");
        OnSelectionChanged();
    }
}

No meu código xaml eu só preciso disso:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Tsjakka
fonte