Bagaimana cara mengikat enum ke kontrol combobox di WPF?

182

Saya mencoba untuk menemukan contoh sederhana di mana enums ditampilkan sebagaimana adanya. Semua contoh yang saya lihat mencoba menambahkan string tampilan yang terlihat bagus tapi saya tidak ingin kompleksitas itu.

Pada dasarnya saya memiliki kelas yang menampung semua properti yang saya ikat, dengan terlebih dahulu mengatur DataContext ke kelas ini, dan kemudian menentukan pengikatan seperti ini di file xaml:

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

Tapi ini tidak menunjukkan nilai enum di ComboBoxitem sebagai.

Joan Venge
sumber
9
Inilah yang Anda cari: WPF ObjectDataProvider - Binding Enum to ComboBox Anda juga dapat mengunduh contoh kode sumber lengkap dari sana.
Jawaban terbaik menurut saya adalah di: stackoverflow.com/questions/58743/…
gimpy
Kemungkinan duplikat dari Penyatuan data properti enum ke ComboBox di WPF
UuDdLrLrSs

Jawaban:

306

Anda dapat melakukannya dari kode dengan menempatkan kode berikut di Window Loadedevent handler, misalnya:

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

Jika Anda perlu mengikatnya di XAML, Anda perlu menggunakan ObjectDataProvideruntuk membuat objek yang tersedia sebagai sumber yang mengikat:

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

Menarik perhatian pada kode selanjutnya:

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

Panduan bagaimana memetakan namespace dan assembly yang dapat Anda baca di MSDN .

Kyrylo M.
sumber
1
Contoh yang diuji dari tautan pertama, berfungsi OK. Lihat kode dan komentar yang ditambahkan dalam jawaban saya.
Kyrylo M
1
Menemukan masalah Anda di forum MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Cobalah untuk membersihkan dan membangun kembali proyek. Mungkin Anda harus menanyakan masalah itu di sini pada pertanyaan lain. Ini adalah satu-satunya saran saya ... Bagaimanapun, contoh yang ditampilkan adalah benar.
Kyrylo M
1
Terima kasih, itu aneh tapi saya telah melihat hal serupa dengan kegilaan WPF. Akan lakukan dan beri tahu Anda. Btw apakah ini masalah yang sama dijelaskan di sini: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge
2
Anda perlu menambahkan referensi padanya dan menambahkan xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"XAML untuk menggunakannya. Berikut ini panduan: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M
4
Anda dapat menggunakan alat-alat seperti ReSharper. Ini mem-parsing semua rujukan referensi dan memberikan saran apa yang perlu dimasukkan. Tidak perlu menulis - cukup pilih dari opsi.
Kyrylo M
117

Saya suka untuk semua objek yang saya ikat harus didefinisikan di saya ViewModel, jadi saya mencoba untuk menghindari menggunakan <ObjectDataProvider>xaml jika memungkinkan.

Solusi saya tidak menggunakan data yang ditentukan dalam Lihat dan tidak ada kode di belakang. Hanya DataBinding, ValueConverter yang dapat digunakan kembali, metode untuk mendapatkan koleksi deskripsi untuk semua jenis Enum, dan satu properti di ViewModel untuk diikat.

Ketika saya ingin mengikat sebuah Enumke ComboBoxteks saya ingin menampilkan tidak pernah cocok dengan nilai-nilai Enum, jadi saya menggunakan [Description()]atribut untuk memberikan teks yang sebenarnya ingin saya lihat di ComboBox. Jika saya memiliki enum hari dalam seminggu, itu akan terlihat seperti ini:

public enum DayOfWeek
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Sunday")]
  SUNDAY,
  [Description("Monday")]
  MONDAY,
  ...
}

Pertama saya membuat kelas pembantu dengan beberapa metode untuk menangani enum. Satu metode mendapatkan deskripsi untuk nilai tertentu, metode lain mendapatkan semua nilai dan deskripsi mereka untuk suatu tipe.

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

Selanjutnya, kita buat a ValueConverter. Mewarisi dari MarkupExtensionmembuatnya lebih mudah digunakan di XAML sehingga kami tidak harus mendeklarasikannya sebagai sumber daya.

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

ViewModelSatu-satunya kebutuhan saya adalah 1 properti yang Viewdapat saya ikat untuk keduanya SelectedValuedan ItemsSourcekotak kombo:

private DayOfWeek dayOfWeek;

public DayOfWeek SelectedDay
{
  get { return dayOfWeek; }
  set
  {
    if (dayOfWeek != value)
    {
      dayOfWeek = value;
      OnPropertyChanged(nameof(SelectedDay));
    }
  }
}

Dan akhirnya untuk mengikat ComboBoxtampilan (menggunakan ValueConverterdalam ItemsSourceikatan) ...

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

Untuk mengimplementasikan solusi ini, Anda hanya perlu menyalin EnumHelperkelas dan EnumToCollectionConverterkelas saya. Mereka akan bekerja dengan enum apa pun . Juga, saya tidak memasukkannya di sini, tetapi ValueDescriptionkelas ini hanya kelas sederhana dengan 2 properti objek publik, satu disebut Value, satu disebut Description. Anda dapat membuatnya sendiri atau Anda dapat mengubah kode untuk menggunakan a Tuple<object, object>atauKeyValuePair<object, object>

Nick
sumber
9
Untuk membuat ini berhasil, saya harus membuat ValueDescriptionkelas yang memiliki properti publik untuk ValuedanDescription
Perchik
4
Ya, Anda juga dapat mengubah kode ini untuk menggunakan kelas Tuple<T1, T2>atau KeyValuePair<TKey, TValue>bukan ValueDescriptiondan kemudian Anda tidak perlu membuatnya sendiri.
Nick
Saya perlu menerapkan OnPropertyChanged (atau yang setara) untuk kedua properti ViewModel, bukan hanya SelectedClass.
Will
Anda tidak perlu mengimplementasikan OnPropertyChanged untuk properti yang mengembalikan daftar. Daftar ini dihasilkan dari nilai-nilai dalam Enum. Itu tidak akan pernah berubah selama waktu berjalan, dan ketika tidak pernah berubah, tidak perlu memberi tahu siapa pun bahwa itu telah berubah. Juga, dengan versi yang diperbarui, properti daftar bahkan tidak diperlukan sama sekali.
Nick
Bagaimana ItemSource combobox dan SelectedValue adalah properti yang sama. Apakah ItemsSource tidak perlu daftar? Oh, begitu, karena EnumHelper membuat daftar objek. ini sebenarnya membuat ViewModel saya lebih sederhana karena saya tidak harus memelihara daftar objek yang terpisah untuk mengisi ItemSource.
Stealth Rabbi
46

Saya menggunakan solusi lain menggunakan MarkupExtension.

  1. Saya membuat kelas yang menyediakan sumber item:

    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. Itu hampir semua ... Sekarang gunakan di XAML:

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

tom.maruska
sumber
1
@Nick: Jawaban yang diterima adalah referensi enum (atau model seperti yang Anda katakan) di xaml juga. Solusi Anda membuat 2 properti dan bidang dukungan dalam model tampilan, yang saya tidak suka (aturan KERING). Dan tentu saja, Anda tidak harus menggunakan e.ToString()nama tampilan. Anda dapat menggunakan penerjemah Anda sendiri, pengurai atribut keterangan, apa pun.
tom.maruska
2
@ tom.maruska Saya tidak mencoba masuk ke jawaban saya vs jawaban Anda, tetapi karena Anda mengemukakannya, memiliki 2 properti tidak melanggar aturan KERING ketika mereka adalah 2 properti berbeda yang memiliki tujuan berbeda. Dan jawaban Anda juga akan memerlukan penambahan properti (Anda bahkan menyebut-nyebut properti ini sendiri {Binding Path=WhereEverYouWant}) dan jika Anda ingin mendukung pengikatan 2 arah, Anda juga akan memiliki bidang dukungan. Jadi Anda tidak mengganti 2 properti dan 1 bidang dukungan dengan melakukan ini, Anda hanya mengganti 1 properti hanya baca satu baris.
Nick
@Nick Ya, Anda benar tentang properti dan bidang dukungan :)
tom.maruska
24

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

berdasarkan artikel ini

druss
sumber
4
Solusi yang sangat sederhana. Namespace for System seperti pada jawaban kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite
Berfungsi dengan baik dalam proyek-proyek WPF Visual Studio 2017.
Sorush
11

Jawaban Nick benar-benar membantu saya, tetapi saya menyadari itu bisa sedikit diubah, untuk menghindari kelas tambahan, ValueDescription. Saya ingat bahwa sudah ada kelas KeyValuePair yang sudah ada dalam framework, jadi ini bisa digunakan.

Kode hanya berubah sedikit:

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

dan akhirnya XAML:

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

Saya harap ini bermanfaat bagi orang lain.

Roger
sumber
Implementasi pertama saya memang menggunakan a KeyValuePairtetapi pada akhirnya saya memutuskan menggunakan a KeyValuePairuntuk mewakili sesuatu yang bukan pasangan kunci-nilai hanya untuk menghindari penulisan kelas sederhana yang sepele tidak masuk akal. The ValueDescriptionkelas hanya 5 baris, dan 2 dari mereka hanya {dan}
Nick
8

Anda harus membuat array nilai di enum, yang dapat dibuat dengan memanggil System.Enum.GetValues ​​() , meneruskannya Typedengan enum yang Anda inginkan dari item.

Jika Anda menentukan ini untuk ItemsSourceproperti, maka itu harus diisi dengan semua nilai enum. Anda mungkin ingin mengikat SelectedItemke EffectStyle(dengan asumsi itu adalah properti dari enum yang sama, dan mengandung nilai saat ini).

Andy
sumber
Terima kasih, bisakah Anda menunjukkan bagian pertama dalam kode? Saya tidak yakin di mana menyimpan nilai enum sebagai array? Properti enum terletak di kelas lain. Bisakah saya melakukan langkah GetValues ​​ini di dalam xaml?
Joan Venge
4

Semua posting di atas telah melewatkan trik sederhana. Dimungkinkan dari pengikatan SelectedValue untuk mengetahui cara mengisi ItemSource secara OTOMATIS sehingga markup XAML Anda adil.

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

Sebagai contoh di ViewModel saya yang saya miliki

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


    FoolEnum _Fool;
    public FoolEnum Fool
    {
        get { return _Fool; }
        set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
    }

ValidateRaiseAndSetIfChanged adalah kait INPC saya. Milik Anda mungkin berbeda.

Implementasi EnumComboBox adalah sebagai berikut, tetapi pertama-tama saya perlu sedikit pembantu untuk mendapatkan string dan nilai enumerasi saya.

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

dan kelas utama (Catatan saya menggunakan ReactiveUI untuk mengaitkan perubahan properti melalui 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";

        }
    }
}

Anda juga perlu mengatur gaya dengan benar di Generic.XAML atau kotak Anda tidak akan membuat apa pun dan Anda akan mencabut rambut Anda.

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

dan itu dia. Ini jelas dapat diperluas untuk mendukung i18n tetapi akan membuat posting lebih lama.

bradgonesurfing
sumber
3

Aplikasi universal tampaknya bekerja sedikit berbeda; itu tidak memiliki semua kekuatan XAML berfitur lengkap. Apa yang berhasil untuk saya adalah:

  1. Saya membuat daftar nilai enum sebagai enum (tidak dikonversi ke string atau bilangan bulat) dan mengikat ComboBox ItemsSource dengan itu
  2. Lalu saya bisa mengikat ComboBox ItemSelected ke properti publik saya yang tipenya adalah enum yang dimaksud

Hanya untuk bersenang-senang saya menyiapkan kelas templated kecil untuk membantu dengan ini dan menerbitkannya ke halaman Sampel MSDN . Bit tambahan membiarkan saya secara opsional mengganti nama enum dan membiarkan saya menyembunyikan beberapa enum. Kode saya terlihat seperti Nick (di atas), yang saya harap telah saya lihat sebelumnya.

Menjalankan sampel;  ini mencakup beberapa ikatan twoway pada enum

PESMITH_MSFT
sumber
3

Ada banyak jawaban bagus untuk pertanyaan ini dan saya dengan rendah hati mengirimkan pertanyaan saya. Saya menemukan bahwa milik saya agak lebih sederhana dan lebih elegan. Ini hanya membutuhkan konverter nilai.

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

dan konverter nilai ...

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

sumber daya ...

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

Deklarasi XAML ...

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

Lihat model ...

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

Menghasilkan kotak kombo ...

ComboBox terikat ke enum

AQuirky
sumber
Bagi saya, ini adalah solusi terbaik untuk pertanyaan: sederhana, mudah dimengerti, mudah diterapkan.
Informagic
Masalah dengan solusi ini adalah tidak dapat dilokalisasi.
Robin Davies
@RobinDavies Anda dapat melokalkannya. Membutuhkan Keterangan khusus atribut yang saya buat beberapa. Lihat pertanyaan SO ini untuk beberapa ide: 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));
    }
}

Anda harus memperluas jawaban Rogers dan Greg dengan semacam konverter nilai Enum, jika Anda mengikat langsung ke properti model objek.

Ruberoid
sumber
1

Jika Anda mengikat ke properti enum aktual di ViewModel Anda, bukan representasi int dari enum, segalanya menjadi rumit. Saya menemukan perlu untuk mengikat ke representasi string, BUKAN nilai int seperti yang diharapkan dalam semua contoh di atas.

Anda dapat mengetahui apakah ini masalahnya dengan mengikat kotak teks sederhana ke properti yang ingin Anda ikat pada ViewModel Anda. Jika menampilkan teks, ikat ke string. Jika itu menunjukkan angka, ikat ke nilainya. Catatan Saya telah menggunakan Tampilan dua kali yang biasanya merupakan kesalahan, tapi itu satu-satunya cara kerjanya.

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

Greg

Greg Gum
sumber
Jawaban ini tampaknya tidak lengkap: * Apa itu / core /?
trapicki
1

Saya menyukai jawaban tom.maruska , tetapi saya perlu mendukung semua jenis enum yang mungkin ditemui templat saya saat runtime. Untuk itu, saya harus menggunakan penjilidan untuk menentukan jenis ke ekstensi markup. Saya bisa bekerja dalam jawaban ini dari nicolay.anykienko untuk datang dengan ekstensi markup yang sangat fleksibel yang akan bekerja dalam hal apa pun yang dapat saya pikirkan. Dikonsumsi seperti ini:

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

Sumber untuk ekstensi markup tumbuk yang dirujuk di atas:

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
sumber
1

Penjelasan sederhana dan jelas: 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
sumber
0

Dengan menggunakan ReactiveUI, saya telah membuat solusi alternatif berikut. Ini bukan solusi all-in-one yang elegan, tapi saya pikir paling tidak itu bisa dibaca.

Dalam kasus saya, mengikat daftar enumke kontrol adalah kasus yang jarang terjadi, jadi saya tidak perlu skala solusi di seluruh basis kode. Namun, kode dapat dibuat lebih umum dengan mengubah EffectStyleLookup.Itemmenjadi Object. Saya mengujinya dengan kode saya, tidak ada modifikasi lain yang diperlukan. Yang berarti satu kelas helper dapat diterapkan ke enumdaftar mana pun . Meskipun itu akan mengurangi keterbacaannya - ReactiveList<EnumLookupHelper>tidak memiliki cincin yang bagus untuk itu.

Menggunakan kelas pembantu berikut:

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

Di ViewModel, konversi daftar enum dan paparkan sebagai properti:

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

Di ComboBox, manfaatkan SelectedValuePathproperti, untuk mengikat ke nilai asli enum:

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

Dalam View, ini memungkinkan kita untuk mengikat aslinya enumke SelectedEffectStyledalam ViewModel, tetapi menampilkan ToString()nilai dalam 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
sumber
Saya pikir ViewModel Anda memiliki kesalahan. 1) Bukankah seharusnya itu ReactiveList of EffectStyleLookup ?, 2) Anda harus membuat ReactiveList kosong <T> () terlebih dahulu. Kemudian tambahkan item. Akhirnya: ReactiveList <T> sekarang sudah tidak digunakan lagi (tetapi masih berfungsi). EffectStyles = ReactiveList baru <EffectStyleLookup> (); EffectStyles.AddRange (daftar); Terima kasih telah meluangkan waktu untuk menunjukkan ini.
user1040323
0

Saya menambahkan komentar saya (dalam VB, sayangnya, tetapi konsepnya dapat dengan mudah direplikasi ke C # dalam sekejap), karena saya hanya perlu referensi ini dan tidak menyukai jawaban karena mereka terlalu kompleks. Seharusnya tidak sesulit ini.

Jadi saya menemukan cara yang lebih mudah. Ikat Enumerator ke dalam Kamus. Bind kamus itu ke Combobox.

Kotak kombo saya:

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

Kode saya di belakang. Semoga ini bisa membantu orang lain keluar.

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-laki Politis
sumber
Jawaban Kyrylo jauh lebih sederhana daripada jawaban Anda - saya tidak mengerti apa yang rumit tentang itu? Nya memerlukan nol konversi dalam kode.
Johnathon Sullinger
Saya tidak ingin menempatkan semua logika saya ke tangan XAML. Saya lebih suka melakukan logika dengan cara saya (tidak selalu cara terbaik), tetapi memungkinkan saya untuk memahami di mana dan mengapa sesuatu tidak berjalan sesuai rencana. Nya kurang rumit, tetapi bergantung pada XAML / WPF untuk melakukan logika. Aku bukan penggemar itu. 10.000 cara menguliti kucing, Anda tahu?
Laki Politis
Cukup adil. Saya pribadi lebih suka menggunakan fitur yang sudah dibangun, di luar kotak, untuk saya tetapi itu hanya preferensi saya;) Untuk masing-masing ada sendiri!
Johnathon Sullinger
Ya pak! Saya sangat mengerti. Saya telah dipaksa dalam pengembangan Perangkat Lunak yang berasal dari pengembangan web. Saya belum begitu mutakhir di WPF dan harus belajar banyak seperti yang saya lakukan. Saya masih tidak mengerti semua seluk-beluk kontrol WPF / XAML, dan karena itu saya telah menemukan lebih banyak masalah daripada solusi dalam cara saya mengharapkan sesuatu bekerja. Tapi saya menghargai percakapan ini. Itu membuat saya melakukan penelitian lebih lanjut.
Laki Politis
0

Solusi Nick dapat lebih disederhanakan, tanpa ada yang mewah, Anda hanya perlu satu konverter:

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

Anda kemudian menggunakan ini di mana pun Anda ingin kotak kombo Anda muncul:

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

Saya tidak akan merekomendasikan menerapkan ini sebagaimana adanya tapi mudah-mudahan ini dapat menginspirasi solusi yang baik

Katakanlah enum Anda adalah Foo. Maka Anda dapat melakukan sesuatu seperti ini.

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

Kemudian pada Window.Loadmetode Anda dapat memuat semua enums ke ObservableCollection<FooViewModel>yang Anda dapat mengatur sebagai DataContext dari combobox.

Shaamil Ahmed
sumber
0

Saya hanya membuatnya sederhana. Saya membuat daftar item dengan nilai enum di ViewModel saya:

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

Dalam kode xaml saya, saya hanya perlu ini:

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