Databinding properti enum ke ComboBox di WPF

256

Sebagai contoh, ambil kode berikut:

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

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

Saya ingin databind properti ExampleProperty ke ComboBox, sehingga itu menunjukkan opsi "FooBar" dan "BarFoo" dan bekerja dalam mode TwoWay. Secara optimal saya ingin definisi ComboBox saya terlihat seperti ini:

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

Saat ini saya memiliki penangan untuk acara ComboBox.SelectionChanged dan ExampleClass.PropertyChanged diinstal di Window saya di mana saya melakukan penjilidan secara manual.

Apakah ada cara kanonik yang lebih baik atau semacamnya? Apakah Anda biasanya menggunakan Pengonversi dan bagaimana Anda mengisi ComboBox dengan nilai yang benar? Saya bahkan tidak ingin memulai dengan i18n sekarang.

Edit

Jadi satu pertanyaan dijawab: Bagaimana cara mengisi ComboBox dengan nilai yang benar.

Ambil nilai Enum sebagai daftar string melalui ObjectDataProvider dari metode Enum.GetValues ​​statis:

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

Ini bisa saya gunakan sebagai ItemsSource untuk ComboBox saya:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>
Maximilian
sumber
4
Saya menjelajahi ini dan memiliki solusi yang dapat Anda gunakan (lengkap dengan lokalisasi) di WPF yang terletak di sini .
ageektrapped

Jawaban:

208

Anda dapat membuat ekstensi markup kustom.

Contoh penggunaan:

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

Di bagian atas XAML Anda:

    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

lalu...

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

Dan implementasinya ...

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; }
    }
  }
Gregor Slavec
sumber
7
@ Gregor S. what my: Enumeration is?
joshua
14
@Crown 'my' adalah awalan namespace yang Anda deklarasikan di atas Anda file xaml: mis. Xmlns: my = "clr-namespace: namespace_to_enumeration_extension_class. Enumerasi adalah kependekan dari EnumerationExtension, di xaml Anda tidak perlu menulis seluruh nama kelas ekstensi .
Gregor Slavec
33
+1, tetapi jumlah kode yang diperlukan WPF untuk mencapai hal-hal paling sederhana benar-benar headspinning
Konrad Morawski
1
Saya tidak begitu suka cara itu membuat Anda menggunakan referensi ke bagian dari model Anda - jenis enumerasi - dalam tampilan, di ItemsSourceparam. Untuk menjaga agar tampilan dan model tetap terpisah saya perlu membuat salinan enumerasi dalam ViewModel dan kode ViewModel untuk menerjemahkan di antara keduanya ... Yang akan membuat solusi tidak sesederhana itu lagi. Atau adakah cara untuk memasok tipe itu sendiri dari ViewModel?
lampak
6
Keterbatasan lain adalah Anda tidak dapat melakukan ini jika Anda memiliki banyak bahasa.
River-Claire Williamson
176

Dalam model tampilan Anda dapat memiliki:

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

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

Di XAML ItemSourcemengikat ke MyEnumTypeValuesdan SelectedItemmengikat ke SelectedMyEnumType.

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>
pengguna659130
sumber
Ini bekerja luar biasa di aplikasi Universal saya, dan sangat mudah diimplementasikan. Terima kasih!
Nathan Strutz
96

Saya lebih suka tidak menggunakan nama enum di UI. Saya lebih suka menggunakan nilai yang berbeda untuk pengguna ( DisplayMemberPath) dan berbeda untuk nilai (enum dalam kasus ini) ( SelectedValuePath). Kedua nilai tersebut dapat dikemas KeyValuePairdan disimpan dalam kamus.

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: Kompatibel dengan pola MVVM.

CoperNick
sumber
14
Saya pikir jawaban Anda diremehkan, sepertinya opsi terbaik yang diberikan ComboBox sendiri. Mungkin Anda bisa meletakkan pembuat kamus di pengambil, menggunakan Enum.GetValues, tetapi itu tidak akan menyelesaikan bagian dari nama yang akan ditampilkan. Pada akhirnya, dan khususnya jika I18n diimplementasikan, Anda harus mengubah hal-hal secara manual jika enum berubah. Tetapi enum tidak seharusnya sering berubah, jika memang demikian, bukan? +1
heltonbiker
2
Jawaban ini luar biasa DAN memungkinkan untuk melokalkan deskripsi enum ... Terima kasih untuk ini!
Shay
2
Solusi ini sangat baik karena menangani enum dan lokalisasi dengan kode lebih sedikit daripada solusi lain!
hfann
2
Masalah dengan Kamus adalah bahwa kunci-kunci tersebut diurutkan berdasarkan nilai hash sehingga hanya ada sedikit kontrol atas itu. Meskipun sedikit lebih verbose, saya menggunakan List <KeyValuePair <enum, string >>. Ide bagus.
Kevin Brock
3
@CoperNick @Pragmateek perbaikan baru: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

Saya tidak tahu apakah mungkin hanya di XAML tetapi coba yang berikut ini:

Beri nama ComboBox Anda sehingga Anda dapat mengaksesnya di codebehind: "typesComboBox1"

Sekarang coba yang berikut ini

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));
rudigrobler
sumber
24

Berdasarkan jawaban yang diterima tetapi sekarang dihapus yang disediakan oleh ageektrapped, saya membuat versi langsing tanpa beberapa fitur yang lebih canggih. Semua kode disertakan di sini untuk memungkinkan Anda menyalin-menempelnya dan tidak diblokir oleh tautan-busuk.

Saya menggunakan System.ComponentModel.DescriptionAttributeyang benar-benar dimaksudkan untuk deskripsi waktu desain. Jika Anda tidak suka menggunakan atribut ini, Anda dapat membuatnya sendiri, tetapi saya pikir menggunakan atribut ini benar-benar menyelesaikan pekerjaan. Jika Anda tidak menggunakan atribut, nama akan default ke nama nilai enum dalam kode.

public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

Berikut adalah kelas yang digunakan sebagai sumber item:

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

}

Anda dapat menggunakannya di XAML seperti ini:

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

Gunakan ObjectDataProvider:

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

dan kemudian ikat ke sumber daya statis:

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

Temukan solusi ini di blog ini

druss
sumber
Jawaban bagus. Kebetulan, itu menyelamatkan Anda dari harus khawatir tentang masalah Converterenum-to-string.
DonBoitnott
1
Solusi Terkait tampaknya mati (Teks Korea atau Jepang?). Jika saya meletakkan kode Anda ke Sumber Daya XAML saya dikatakan Enum tidak didukung dalam proyek WPF.
Sebastian
6

Cara favorit saya untuk melakukan ini adalah dengan ValueConvertersehingga ItemsSource dan SelectedValue keduanya mengikat ke properti yang sama. Ini tidak memerlukan properti tambahan untuk menjaga ViewModel Anda tetap bagus dan bersih.

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

Dan definisi Konverter:

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

Konverter ini akan bekerja dengan enum apa pun. ValueDescriptionhanya kelas sederhana dengan Valueproperti dan Descriptionproperti. Anda bisa dengan mudah menggunakan a Tupledengan Item1dan Item2, atau KeyValuePairdengan Keydan Valuealih-alih Nilai dan Deskripsi atau kelas lain pilihan Anda asalkan itu dapat memiliki nilai enum dan deskripsi string dari nilai enum itu.

Nick
sumber
Jawaban bagus! Untuk ValueDescriptionkelas, Descriptionproperti dapat dihilangkan jika tidak diperlukan. Kelas sederhana dengan hanya Valueproperti juga berfungsi!
pogosama
Juga, jika Anda ingin mengikat RadioButton, maka metode Konversi harus mengembalikan daftar string, yaitu .Select(e => e.ToString()), alih-alih menggunakan ValueDescriptionkelas.
pogosama
Alih-alih ValueDescriptionjuga KeyValuePairbisa digunakan, seperti yang ditunjukkan di sini
Apfelkuacha
5

Berikut ini adalah solusi umum menggunakan metode pembantu. Ini juga dapat menangani enum dari jenis apa pun yang mendasarinya (byte, sbyte, uint, long, dll.)

Metode Penolong:

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

Lihat Model:

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

Kotak kombo:

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

Anda dapat mempertimbangkan sesuatu seperti itu:

  1. tentukan style untuk textblock, atau kontrol lain yang ingin Anda gunakan untuk menampilkan enum Anda:

    <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. tentukan gaya Anda untuk ComboBoxItem

    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  3. tambahkan kotak kombo dan muat dengan nilai enum Anda:

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

jika enum Anda besar, tentu saja Anda dapat melakukan hal yang sama dalam kode, menghemat banyak mengetik. Saya suka pendekatan itu, karena itu membuat lokalisasi mudah - Anda mendefinisikan semua template sekali, dan kemudian, Anda hanya memperbarui file sumber daya string Anda.

Greg
sumber
the SelectedValuePath = "Konten" membantu saya di sini. Saya memiliki ComboBoxItem sebagai nilai string, dan terus tidak dapat mengonversi ComboBoxItem ke Tipe Enum saya. Terima kasih
adriaanp
2

Jika Anda menggunakan MVVM, berdasarkan jawaban @rudigrobler Anda dapat melakukan hal berikut:

Tambahkan properti berikut ke kelas ViewModel

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

Kemudian di XAML lakukan hal berikut:

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

Ini adalah DevExpressjawaban spesifik berdasarkan jawaban terpilih teratas Gregor S.(saat ini memiliki 128 suara).

Ini berarti kami dapat menjaga gaya konsisten di seluruh aplikasi:

masukkan deskripsi gambar di sini

Sayangnya, jawaban asli tidak bekerja dengan ComboBoxEditdari DevExpress tanpa beberapa modifikasi.

Pertama, XAML untuk 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"/>

Tidak perlu dikatakan, Anda harus menunjuk xamlExtensionspada namespace yang berisi kelas ekstensi XAML (yang didefinisikan di bawah):

xmlns:xamlExtensions="clr-namespace:XamlExtensions"

Dan kita harus menunjuk myEnumpada namespace yang berisi enum:

xmlns:myEnum="clr-namespace:MyNamespace"

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

Masalah dengan XAML adalah bahwa kita tidak dapat menggunakan SelectedItemValue, karena ini melempar kesalahan karena setter tidak dapat diakses (sedikit kelalaian pada bagian Anda, DevExpress). Jadi kita harus memodifikasi kita ViewModeluntuk mendapatkan nilai langsung dari objek:

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

Untuk kelengkapan, berikut adalah ekstensi XAML dari jawaban asli (sedikit diganti namanya):

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

Penafian: Saya tidak memiliki afiliasi dengan DevExpress. Telerik juga merupakan perpustakaan yang bagus.

Contango
sumber
Sebagai catatan, saya tidak berafiliasi dengan DevExpress. Telerik juga memiliki perpustakaan yang sangat bagus, dan teknik ini bahkan mungkin tidak diperlukan untuk perpustakaan mereka.
Contango
0

Coba gunakan

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
    SelectedValue="{Binding Path=ExampleProperty}" />
rudigrobler
sumber
Ini tidak berfungsi. Kotak kombo hanya akan menampilkan teks kosong dan mengubahnya tidak akan melakukan apa-apa. Saya kira melempar konverter di sini akan menjadi solusi terbaik.
Maximilian
0

Saya telah membuat proyek CodePlex open source yang melakukan ini. Anda dapat mengunduh paket NuGet dari sini .

<enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" />
Lawman
sumber