WPF ComboBox bir enum özelliği veri


256

Örnek olarak aşağıdaki kodu alın:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } }
}

Ben bir "ComoBar" ve "BarFoo" seçeneklerini gösterir ve modunda TwoWay çalışır bir ComboBox özelliği SampleProperty databind istiyorum. En iyi benim ComboBox tanımının böyle bir şey görünmesini istiyorum:

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />

Şu anda ComboBox.SelectionChanged ve SampleClass.PropertyChanged olayları için işleyicileri var, burada el ile bağlama yapmak benim pencere yüklü.

Daha iyi veya bir tür kanonik yol var mı? Genellikle Dönüştürücüler kullanır mısınız ve ComboBox'ı doğru değerlerle nasıl doldurursunuz? Şu anda i18n ile başlamak bile istemiyorum.

Düzenle

Böylece bir soru cevaplandı: ComboBox'ı doğru değerlerle nasıl doldururum?

Statik Enum.GetValues ​​yönteminden bir ObjectDataProvider aracılığıyla Enum değerlerini bir dize listesi olarak alın:

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

Bu benim ComboBox için ItemsSource olarak kullanabilirsiniz:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>

4
Bunu keşfettim ve burada bulunan WPF'de (yerelleştirme ile birlikte) kullanabileceğiniz bir çözümüm var .
ageektrapped

Yanıtlar:


208

Özel bir biçimlendirme uzantısı oluşturabilirsiniz.

Kullanım örneği:

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

XAML'nizin üstünde:

    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

ve sonra...

<ComboBox 
    ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" 
    DisplayMemberPath="Description" 
    SelectedValue="{Binding CurrentStatus}"  
    SelectedValuePath="Value"  /> 

Ve uygulama ...

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; }
    }
  }

7
@Gregor S. ne benim: Numaralandırma?
joshua

14
@Crown 'my', xaml dosyanızın üstünde bildirdiğiniz ad alanı önekidir: örneğin xmlns: my = "clr-namespace: namespace_to_enumeration_extension_class. Numaralandırma, EnumerationExtension için kısadır, xaml'de uzantı sınıfının tamamını yazmak zorunda değilsiniz .
Gregor Slavec

33
+1, ancak WPF tarafından en basit şeyleri başarmak için gereken kod miktarı gerçekten çok önemli
Konrad Morawski

1
Modelinizin bir kısmına - numaralandırma türüne - görünümde, ItemsSourceparamda referans vermenizi gerçekten sevmiyorum . Görünümü ve modeli ayrıştırmak için ViewModel ve numaralandırma arasında bir kopyasını oluşturmak için iki arasında çevirmek için ViewModel kodunu gerekir ... Bu çözüm artık bu kadar basit değil. Yoksa türün kendisini ViewModel'den sağlamanın bir yolu var mı?
lampak

6
Başka bir sınırlama, birden fazla diliniz varsa bunu yapamazsınız.
River-Claire Williamson

176

Viewmodel'de şunlar olabilir:

public MyEnumType SelectedMyEnumType 
{
    get { return _selectedMyEnumType; }
    set { 
            _selectedMyEnumType = value;
            OnPropertyChanged("SelectedMyEnumType");
        }
}

public IEnumerable<MyEnumType> MyEnumTypeValues
{
    get
    {
        return Enum.GetValues(typeof(MyEnumType))
            .Cast<MyEnumType>();
    }
}

XAML'de ItemSourcebağlanır MyEnumTypeValuesve SelectedItembağlanır SelectedMyEnumType.

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>

Bu benim Evrensel app inanılmaz çalıştı ve uygulamak çok kolay oldu. Teşekkür ederim!
Nathan Strutz

96

Kullanıcı arayüzünde enum adını kullanmamayı tercih ediyorum. Kullanıcı ( DisplayMemberPath) için farklı değer ve değer için farklı değer (bu durumda numaralandırma) ( SelectedValuePath) kullanmayı tercih ederim . Bu iki değer KeyValuePairsözlüğe paketlenebilir ve sözlükte saklanabilir.

XAML

<ComboBox Name="fooBarComboBox" 
          ItemsSource="{Binding Path=ExampleEnumsWithCaptions}" 
          DisplayMemberPath="Value" 
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" > 

C #

public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
    new Dictionary<ExampleEnum, string>()
    {
        {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: MVVM modeliyle uyumludur.


14
Bence cevabınız önemsiz, ComboBox'ın kendisinin beklediği en iyi seçenek gibi görünüyor. Belki de bir sözlük oluşturucuyu alıcıya koyabilirsiniz Enum.GetValues, ancak bu görüntülenecek adların bir kısmını çözmez. Sonunda ve özellikle I18n uygulanırsa, numaralandırma değiştiyse her şeyi elle değiştirmeniz gerekir. Ancak sıraların sık sık değişmesi gerekmez, öyle mi? +1
heltonbiker

2
Bu cevap harika ve numaralandırma açıklamalarını yerelleştirmek için izin verir ... Bunun için teşekkürler!
Shay

2
Bu çözüm çok iyi çünkü hem enum hem de lokalizasyonu diğer çözümlerden daha az kodla işliyor!
hfann

2
Sözlük ile sorun anahtarları karma değeri tarafından sipariş olmasıdır, bu yüzden bu konuda çok az kontrol var. Biraz daha ayrıntılı olmasına rağmen, List <KeyValuePair <enum, string >> kullandım. İyi fikir.
Kevin Brock

3
@CoperNick @Pragmateek yeni düzeltme:public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } = new Dictionary<ExampleEnum, string>() { {ExampleEnum.FooBar, "Foo Bar"}, {ExampleEnum.BarFoo, "Reversed Foo Bar"}, //{ExampleEnum.None, "Hidden in UI"}, };
Jinjinov

40

Sadece XAML'de mümkün olup olmadığını bilmiyorum ama aşağıdakileri deneyin:

ComboBox'ınıza codebehind'de erişebilmeniz için bir ad verin: "typesComboBox1"

Şimdi aşağıdakileri deneyin

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

24

Ageektrapped tarafından sağlanan kabul edilmiş ancak şimdi silinmiş cevabına dayanarak, bazı daha gelişmiş özellikler olmadan zayıflamış bir versiyon oluşturdum. Tüm kod, kopyalayıp yapıştırmanıza ve link-rot tarafından engellenmemesine izin vermek için buraya dahil edilmiştir.

System.ComponentModel.DescriptionAttributeGerçekten tasarım zamanı açıklamaları için tasarlanmış olanı kullanıyorum . Bu özniteliği kullanmaktan hoşlanmıyorsanız kendi özniteliğinizi oluşturabilirsiniz, ancak bu özniteliği kullanmak gerçekten işi bitirdiğini düşünüyorum. Özniteliği kullanmazsanız, ad varsayılan olarak koddaki enum değerinin adı olur.

public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

Burada item kaynağı olarak kullanılan sınıf verilmiştir:

public class EnumItemsSource : Collection<String>, IValueConverter {

  Type type;

  IDictionary<Object, Object> valueToNameMap;

  IDictionary<Object, Object> 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;
  }

}

XAML'de şu şekilde kullanabilirsiniz:

<Windows.Resources>
  <local:EnumItemsSource
    x:Key="ExampleEnumItemsSource"
    Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
  ItemsSource="{StaticResource ExampleEnumItemsSource}"
  SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/> 

23

ObjectDataProvider kullanın:

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

ve ardından statik kaynağa bağlanın:

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

Bu çözümü bu blogda bulun


Güzel cevap. Bu arada, Converternumaralandırma-dize sorunu için endişelenmenize gerek kalmaz.
DonBoitnott

1
Bağlantılı Çözüm ölü gibi görünüyor (Korece veya Japonca Metin?). Kodunuzu XAML Kaynaklarıma koyarsam, Enum'un WPF projesinde desteklenmediğini söyler.
Sebastian

6

Bunu yapmanın en sevdiğim yolu ValueConverter, ItemsSource ve SelectedValue öğelerinin her ikisinin de aynı özelliğe bağlanmasıdır. Bu , ViewModel'inizi güzel ve temiz tutmak için ek özellik gerektirmez .

<ComboBox ItemsSource="{Binding Path=ExampleProperty, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
          SelectedValuePath="Value"
          DisplayMemberPath="Description"
          SelectedValue="{Binding Path=ExampleProperty}" />

Ve Dönüştürücünün tanımı:

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<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return Enum.GetValues(value.GetType())
               .Cast<Enum>()
               .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;
  }
}

Bu dönüştürücü herhangi bir numaralandırma ile çalışacaktır. ValueDescriptionsadece bir Valueözelliği ve bir Descriptionözelliği ile basit bir sınıftır . Sen gibi kolayca kullanabilir Tupleile Item1ve Item2ya bir KeyValuePairbirlikte Keyve Valueonun yerine Açıklama veya uzun bir enum değeri ve bu enum değerinin dize açıklaması tutabilir sahip olarak seçtiğiniz herhangi bir başka sınıfın Değeri ve.


Güzel cevap! İçin ValueDescriptionsınıf, Descriptiongerekli değilse mülkiyet ihmal edilebilir. Sadece Valuemülkiyet ile basit bir sınıf da çalışır!
pogosama

Ayrıca, bir RadioButton öğesine bağlanmak istiyorsanız, Convert yönteminin bir dize listesi, yani sınıfı .Select(e => e.ToString())kullanmak yerine döndürmesi gerekir ValueDescription.
pogosama

Yerine ValueDescriptionde bir KeyValuePairbenzeri kullanılabilir Burada gösterilen
Apfelkuacha

5

İşte bir yardımcı yöntem kullanan genel bir çözüm. Bu, altta yatan herhangi bir türdeki (bayt, sbyte, uint, uzun vb.) Bir sıralamayı da ele alabilir.

Yardımcı Yöntemi:

static IEnumerable<object> GetEnum<T>() {
    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

Modeli Görüntüle:

public IEnumerable<object> EnumSearchTypes {
    get {
        return GetEnum<SearchTypes>();
    }
}//property

Açılan kutu:

<ComboBox
    SelectedValue       ="{Binding SearchType}"
    ItemsSource         ="{Binding EnumSearchTypes}"
    DisplayMemberPath   ="Name"
    SelectedValuePath   ="Value"
/>

5

böyle bir şey düşünebilirsiniz:

  1. metin bloğu için bir stil veya numaralandırmanızı görüntülemek için kullanmak istediğiniz diğer kontrolleri tanımlayın:

    <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="Text" Value="&lt;NULL&gt;"/>
        <Style.Triggers>
            <Trigger Property="Tag">
                <Trigger.Value>
                    <proj:YourEnum>Value1<proj:YourEnum>
                </Trigger.Value>
                <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/>
            </Trigger>
            <!-- add more triggers here to reflect your enum -->
        </Style.Triggers>
    </Style>
  2. ComboBoxItem için stilinizi tanımlayın

    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  3. bir birleşik giriş kutusu ekleyin ve numaralandırma değerlerinizle yükleyin:

    <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
        <ComboBox.Items>
            <ComboBoxItem>
                <proj:YourEnum>Value1</proj:YourEnum>
            </ComboBoxItem>
        </ComboBox.Items>
    </ComboBox>

numaralandırmanız büyükse, elbette kodda da aynısını yapabilirsiniz ve çok fazla yazmayı engelleyebilirsiniz. yerelleştirmeyi kolaylaştırdığı için bu yaklaşımı beğendim - tüm şablonları bir kez tanımlayın ve sonra yalnızca dize kaynak dosyalarınızı güncelleyin.


SelectedValuePath = "İçerik" burada bana yardımcı oldu. Dize değerleri olarak benim ComboBoxItems var ve alma tuttu ComboBoxItem benim numaralandırma türüne dönüştüremiyor. Teşekkürler
adriaanp

2

@Rudigrobler yanıtını temel alan bir MVVM kullanıyorsanız aşağıdakileri yapabilirsiniz:

ViewModel sınıfına aşağıdaki özelliği ekleyin

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

Sonra XAML'de aşağıdakileri yapın:

<ComboBox ItemsSource="{Binding ExampleEnumValues}" ... />

1

Bu, DevExpresstarafından en çok oy verilen cevaba dayanan özel bir cevaptır.Gregor S. (şu anda 128 oy vardır).

Bu, stili tüm uygulama boyunca tutarlı tutabileceğimiz anlamına gelir:

resim açıklamasını buraya girin

Ne yazık ki, orijinal cevap bir ComboBoxEdit bazı değişiklikler olmadan DevExpress'ten .

İlk olarak, XAML ComboBoxEdit:

<dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}"
    SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    DisplayMember="Description"
    MinWidth="144" Margin="5" 
    HorizontalAlignment="Left"
    IsTextEditable="False"
    ValidateOnTextInput="False"
    AutoComplete="False"
    IncrementalFiltering="True"
    FilterCondition="Like"
    ImmediatePopup="True"/>

Söylemeye gerek yok, xamlExtensionsXAML uzantı sınıfını içeren ad alanını işaret etmeniz gerekecek (aşağıda tanımlanmıştır):

xmlns:xamlExtensions="clr-namespace:XamlExtensions"

Ve myEnumnumaralandırmayı içeren ad alanını işaret etmeliyiz:

xmlns:myEnum="clr-namespace:MyNamespace"

Sonra numaralandırma:

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
    }
}

XAML ile ilgili sorun SelectedItemValue, ayarlayıcı erişilemediğinden (sizin tarafınızdan bir gözetim biraz, DevExpress) bir hata attığından, kullanamayız . Bu nedenle ViewModel, değeri doğrudan nesneden elde etmek için bizim

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");
    }
}

Tamlık için, orijinal yanıttan (biraz yeniden adlandırılmış) XAML uzantısı şöyledir:

namespace XamlExtensions
{
    /// <summary>
    ///     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.
    /// </summary>
    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
    }
}

Yasal Uyarı: DevExpress ile hiçbir ilişkim yok. Telerik aynı zamanda harika bir kütüphanedir.


Kayıt için DevExpress'e bağlı değilim. Telerik'in de çok iyi kütüphaneleri var ve bu teknik kütüphaneleri için bile gerekli olmayabilir.
Contango

0

Kullanmayı deneyin

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
    SelectedValue="{Binding Path=ExampleProperty}" />

Bu işe yaramıyor. Combobox sadece boş bir metin gösterecek ve bunu değiştirmek hiçbir şey yapmayacaktır. Sanırım buraya bir dönüştürücü atmak en iyi çözüm olurdu.
Maximilian

0

Bunu yapan açık kaynaklı bir CodePlex projesi oluşturdum. Sen den Nuget paketini indirebilirsiniz burada .

<enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" />
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.