WPF bir numaralandırma combobox denetimine nasıl bağlanır?


182

Numaralandırmalar olduğu gibi gösterildiği basit bir örnek bulmaya çalışıyorum. Gördüğüm tüm örnekler güzel görünen ekran dizeleri eklemeye çalışıyor, ancak bu karmaşıklığı istemiyorum.

Temelde ilk olarak bu sınıf için DataContext ayarlayıp sonra xaml dosyasında böyle bir bağlanma belirterek, bağladığım tüm özelliklerini tutan bir sınıf var:

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

Ancak bu, ComboBoxas öğelerindeki enum değerlerini göstermez .


9
Burada aradığınız şey: WPF ObjectDataProvider - Enum'u ComboBox'a Bağlama Ayrıca kaynak kod örneğinin tamamını buradan indirebilirsiniz.

Bence en iyi cevap
şurada

Yanıtlar:


307

Aşağıdaki kodu, Windows Loadedolay işleyicisine aşağıdaki kodu yerleştirerek yapabilirsiniz , örneğin:

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

ObjectDataProviderBağlama kaynağı olarak kullanılabilir nesne oluşturmak için XAML'de bağlamanız gerekiyorsa :

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

Sonraki kodda dikkat çekin:

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

MSDN'de okuyabileceğiniz ad alanının ve montajın nasıl eşleneceğine dair kılavuz .


1
İlk bağlantıdan test edilen örnek, Tamam çalışıyor. Eklenen kodu görün ve cevabımda yorum yapın.
Kyrylo M

1
Sorununuzu MSDN forumlarında buldunuz ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Projeyi temizlemeye ve yeniden oluşturmaya çalışın. Muhtemelen burada başka bir soru için bu soruyu sormalısınız. Tavsiye edebileceğim tek şey bu ... Her neyse, gösterilen örnek doğru.
Kyrylo M

1
Teşekkürler, bu tuhaf ama ben wpf delilik ile benzer şeyler gördüm. Yapacak ve size bildirecek. Btw, burada açıklanan aynı problemdir: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Joan Venge

2
Buna referans eklemeniz xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"ve kullanmak için XAML eklemeniz gerekir. İşte kılavuz: msdn.microsoft.com/tr-tr/library/ms747086.aspx
Kyrylo M

4
ReSharper gibi araçları kullanabilirsiniz. Başvurulan tüm derlemeleri ayrıştırır ve neleri içermesi gerektiği konusunda önerilerde bulunur. Yazmaya gerek yok - sadece seçenekler arasından seçim yapın.
Kyrylo M

117

Bağlandığım tüm nesneler için tanımlanmayı seviyorum ViewModel, bu yüzden kullanmaktan kaçınmaya çalışıyorum<ObjectDataProvider> mümkün olduğunca xaml .

Çözümüm, Görünüm'de tanımlanmış veri ve arkada kod kullanmıyor. Yalnızca bir DataBinding, yeniden kullanılabilir ValueConverter, herhangi bir Enum türü için açıklamaların toplanması için bir yöntem ve ViewModel'de bağlanacak tek bir özellik.

Ben görüntülemek istediğiniz Enumbir ComboBoxmetne bir bağlamak istediğinizde asla değerleri ile eşleşmez Enum, bu yüzden [Description()]aslında görmek istediğiniz metni vermek için özniteliği kullanın ComboBox. Haftanın günleri bir numaralandırma olsaydı, şöyle bir şey olurdu:

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

İlk önce enums ile başa çıkmak için birkaç yöntemle yardımcı sınıf oluşturdum. Bir yöntem belirli bir değer için bir açıklama alırken, diğer yöntem bir tür için tüm değerleri ve açıklamalarını alır.

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

Sonra bir ValueConverter. Kaynağından devralmak XAML'de MarkupExtensionkullanımını kolaylaştırır, böylece onu bir kaynak olarak ilan etmek zorunda değiliz.

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

Benim ViewModelsadece 1 özellik ihtiyacı olduğunu benim Viewhem teneke bağlamak SelectedValueve ItemsSourcecombobox:

private DayOfWeek dayOfWeek;

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

Ve nihayet bağlamak için ComboBox(kullanarak görünümü ValueConverteriçinde ItemsSourcebağlanma) ...

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

Bu çözümü uygulamak için sadece EnumHelpersınıfımı ve EnumToCollectionConvertersınıfımı kopyalamanız gerekir . Herhangi bir numaralandırma ile çalışacaklar . Ayrıca, buraya dahil etmedim, ancak ValueDescriptionsınıf, biri denilen Value, biri denilen 2 ortak nesne özelliğine sahip basit bir sınıftır Description. Bunu kendiniz oluşturabilir veya bir Tuple<object, object>veyaKeyValuePair<object, object>


9
Bu işi yapmak ValueDescriptioniçin, halka açık mülkleri olan bir sınıf yaratmak zorunda kaldım ValueveDescription
Perchik

4
Evet, bu kodu sınıfın yerine Tuple<T1, T2>veya KeyValuePair<TKey, TValue>yerine kullanmak için de değiştirebilirsiniz ValueDescriptionve sonra kendinizinkini oluşturmanız gerekmez.
Nick

OnPropertyChanged (veya eşdeğeri) her iki ViewModel özellikleri için, yalnızca SelectedClass uygulamak gerekiyordu.
Will

Listeyi döndüren özellik için OnPropertyChanged uygulamasını uygulamanız gerekmez. Liste bir Enum'daki değerlerden oluşturulur. Çalışma süresi boyunca asla değişmeyecek ve asla değişmediği zaman, kimseye değiştiğini bildirmesi gerekmiyor. Ayrıca, güncellenmiş sürümde list özelliğine bile gerek yoktur.
Nick

Açılan kutunun ItemSource ve SelectedValue aynı özelliği nasıl. ItemsSource'un bir liste olması gerekmez mi? Oh, anlıyorum, EnumHelper nesnelerin bir listesini yapıyor. ItemSource'u doldurmak için ayrı bir nesne listesi tutmam gerektiğinden bu aslında ViewModel'imi basitleştirir.
Stealth Haham

46

MarkupExtension kullanarak başka bir çözüm kullandım.

  1. Öğeleri kaynak sağlayan sınıf yaptım:

    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. Neredeyse hepsi ... Şimdi XAML'de kullanın:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. 'Numaraları değiştir: Devletler'i sıralamanıza değiştirin


1
@Nick: Kabul edilen cevap, xaml'de de numaralandırmaya (veya söylediğiniz gibi modele) atıfta bulunmaktır. Çözümünüz, görünüm modelinde beğenmediğim 2 özellik ve destek alanı oluşturuyor (KURU kuralı). Ve elbette, e.ToString()görünen ad için kullanmak zorunda değilsiniz . Kendi çevirmeninizi, descrtiption nitelik ayrıştırıcısını kullanabilirsiniz.
tom.maruska

2
@ tom.maruska Cevabınıza karşı cevabımın içine girmeye çalışmıyorum, ama siz getirdiğinizden beri 2 özelliğe sahip olmak, farklı amaçlara hizmet eden 2 ayrı özellik olduğunda DRY kuralını ihlal etmiyor. Cevabınız ayrıca bir özellik eklemeyi de gerektirecektir (bu özelliği kendiniz bile çağırdınız {Binding Path=WhereEverYouWant}) ve 2 yönlü bağlamayı desteklemesini istiyorsanız, bunun için bir destek alanınız da olacak. Böylece 2 özelliği ve 1 destek alanını bununla değiştirmiyorsunuz, yalnızca 1 tek satırlık salt okunur özelliği değiştiriyorsunuz.
Nick

@Nick Evet, bu mülk ve destek alanı hakkında haklısın :)
tom.maruska

25

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


4
Mükemmel basit çözüm. Kirmir'in cevabında olduğu gibi Sistem için ad alanı:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite

Visual Studio 2017'nin WPF projelerinde iyi çalışıyor.
Sorush

11

Nick'in cevabı bana gerçekten yardımcı oldu, ancak ekstra bir sınıf olan ValueDescription'dan kaçınmak için biraz değiştirilebileceğini fark ettim. Çerçevede zaten bir KeyValuePair sınıfı olduğunu hatırladım, bu yüzden bunun yerine kullanılabilir.

Kod sadece biraz değişir:

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

ve son olarak XAML:

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

Umarım bu başkalarına yardımcı olur.


İlk uygulamam bir kullandı KeyValuePairama sonunda KeyValuePairbir önemsiz basit sınıf yazmaktan kaçınmak için bir anahtar-değer çifti olmayan bir şeyi temsil etmek için a kullanmaya karar verdim . ValueDescriptionSınıf yalnızca 5 satır olduğunu ve bunlardan 2 sadece vardır {ve}
Nick

8

Sen arayarak oluşturulabilir enum değerler, bir dizi oluşturmanız gerekir ) (System.Enum.GetValues geçirmeden, Typesen öğeleri istediğiniz enum.

Bunu ItemsSourceözellik için belirtirseniz , enum'un tüm değerleriyle doldurulmalıdır. Muhtemelen bağlamak istiyorum SelectedItemiçin EffectStyle(aynı enum bir özelliktir ve mevcut değeri içeren varsayarak).


Teşekkürler, koddaki ilk bölümü gösterebilir misiniz lütfen? Enum değerlerini dizi olarak saklamak için emin değilim? Enum özelliği başka bir sınıfta bulunur. Bu GetValues ​​adımını xaml içinde yapabilir miyim?
Joan Venge

4

Yukarıdaki tüm mesajlar basit bir numarayı kaçırdı. SelectedValue'un bağlanmasıyla, ItemsSource'u OTOMATİK OLARAK nasıl dolduracağınızı öğrenmek, böylece XAML işaretlemenizin sadece olması mümkündür.

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

Örneğin benim ViewModel'imde

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged benim INPC kanca. Sizinki farklı olabilir.

EnumComboBox uygulaması aşağıdaki gibidir ancak önce numaralandırma dizeleri ve değerleri almak için biraz yardımcıya ihtiyacım olacak

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

ve ana sınıf (Not ReacetiveUI'yi WhenAny aracılığıyla özellik değişikliklerini bağlamak için kullanıyorum)

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

        }
    }
}

Ayrıca stili Generic.XAML'de doğru bir şekilde ayarlamanız gerekir veya kutunuz hiçbir şey oluşturmaz ve saçınızı dışarı çıkarırsınız.

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

ve işte bu. Bu açıkça i18n'yi destekleyecek şekilde genişletilebilir, ancak gönderiyi daha uzun hale getirir.


3

Evrensel uygulamalar biraz farklı çalışıyor gibi görünüyor; tam özellikli XAML'nin tüm gücüne sahip değil. Benim için işe yarayan:

  1. Numaralandırmalar (dize veya tamsayılara dönüştürülmemiş) numaralandırma olarak bir liste oluşturdum ve ComboBox ItemsSource buna bağlı
  2. Sonra ComboBox ItemSelected tür söz konusu numaralandırma olan ortak mülk bağlanabilir

Sadece eğlence için bu konuda yardımcı olmak için biraz templated sınıf çırpılmış ve MSDN Örnekleri sayfalarında yayınladı . Ekstra bitler isteğe bağlı olarak numaralandırmaların adlarını geçersiz kılmama ve numaralandırmalardan bazılarını gizlememe izin veriyor. Kodum daha önce görmüş olsaydım Nick'in (yukarıda) gibi korkunç görünüyor.

Numunenin çalıştırılması;  numaralandırmaya çoklu iki yönlü bağlamalar içerir


3

Bu sorunun birçok mükemmel yanıtı var ve ben de alçakgönüllülükle benimkini gönderiyorum. Benimkinin biraz daha basit ve zarif olduğunu düşünüyorum. Yalnızca bir değer dönüştürücü gerektirir.

Bir numaralandırma verildi ...

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
}

ve bir değer dönüştürücü ...

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

kaynakları ...

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

XAML bildirimi ...

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

Modeli görüntüle ...

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

Sonuç açılan kutusu ...

Numaralandırmaya bağlı ComboBox


Benim için, bu sorunun en iyi çözümü: basit, anlaşılması kolay, uygulanması kolay.
Informagic

Bu çözümün sorunu, yerelleştirilemez olmasıdır.
Robin Davies

@RobinDavies yerelleştirebilirsiniz. Birkaç oluşturduğum özel bir açıklama gerektirir. Bazı fikirler için bu SO sorusuna bakın: 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));
    }
}

Doğrudan nesne modeli özelliklerine bağlanıyorsanız, Rogers ve Greg'in cevabını bu tür Enum değeri dönüştürücü ile genişletmelisiniz.


1

ViewModel'inizde gerçek bir enum özelliğine bağlanırsanız, bir enum'un int temsilini değil, işler zorlaşır. Yukarıdaki örneklerin tümünde beklendiği gibi değil dize gösterimine değil, int değeri bağlamak için gerekli bulundu.

Durumun bu olup olmadığını ViewModel üzerinde bağlamak istediğiniz özelliğe basit bir metin kutusu bağlayarak söyleyebilirsiniz. Metin gösteriliyorsa, dizeye bağlanın. Bir sayı gösteriyorsa, değere bağlayın. Not Ekranı normalde bir hata olacak şekilde iki kez kullandım, ancak bu tek şekilde çalışıyor.

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

Greg


Bu cevap eksik görünüyor: * / core / nedir?
trapicki

1

Tom.maruska'nın cevabını beğendim , ancak şablonumun çalışma zamanında karşılaşabileceği herhangi bir enum türünü desteklemem gerekiyordu. Bunun için, biçimlendirme uzantısının türünü belirtmek üzere bir bağlayıcı kullanmak zorunda kaldım. Aklıma gelen her durumda çalışacak çok esnek bir biçimlendirme uzantısı ile gelmek için nicolay.anykienko bu cevapta çalışabildim. Bu şekilde tüketilir:

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

Yukarıda başvurulan mashed up işaretleme uzantısının kaynağı:

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

1

Basit ve açık bir açıklama: 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>

0

Kullanarak ReactiveUI, aşağıdaki alternatif çözümü oluşturdum. Zarif bir hepsi bir arada çözüm değil, ama en azından okunabilir olduğunu düşünüyorum.

Benim durumumda, enumbir denetimin listesini bağlama nadir bir durumdur, bu yüzden çözüm kod tabanı genelinde ölçeklemek gerekmez. Ancak, kod bir olarak değiştirilerek daha genel hale EffectStyleLookup.Itemgetirilebilir Object. Kodumla test ettim, başka değişiklik gerekli değil. Bu, bir yardımcı sınıfın herhangi bir enumlisteye uygulanabileceği anlamına gelir . Bu okunabilirliğini azaltsa da -ReactiveList<EnumLookupHelper> harika bir yüzüğü yok.

Aşağıdaki yardımcı sınıfı kullanarak:

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

ViewModel'de, numaralandırma listesini dönüştürün ve bir özellik olarak gösterin:

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

Olarak ComboBox, kullanmak SelectedValuePathorijinal bağlanma, mülk enumdeğeri:

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

Görünüm, bu orijinal bağlamak bizi tanır enumiçin SelectedEffectStyleViewModel, ancak görüntüler ToString()değeri 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) );
});

Bence ViewModel cihazınızda bir hata var. 1) EffectStyleLookup'un bir ReactiveList'i olmamalı mı ?, 2) Önce boş bir ReactiveList <T> () yapmalısınız. Ardından öğeleri ekleyin. Son olarak: ReactiveList <T> artık kullanımdan kaldırıldı (ancak yine de çalışıyor). EffectStyles = yeni ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (liste); Bunu göstermek için zaman ayırdığınız için teşekkür ederiz.
user1040323

0

Yorumumu ekliyorum (VB'de, ne yazık ki, ancak kavram bir kalp atışı içinde C #'a kolayca çoğaltılabilir), çünkü sadece buna başvurmak zorunda kaldım ve çok karmaşık oldukları için herhangi bir cevabı beğenmedim. Bu kadar zor olmamalı.

Böylece daha kolay bir yol buldum. Sayıcıları sözlüğe bağlar. Sözlüğü Combobox'a bağlayın.

Açılan kutum:

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

Kodumun arkasında. Umarım, bu başka birine yardımcı olur.

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

Kyrylo'nun cevabı sizinkinden çok daha basit - bununla ilgili neyin karmaşık olduğunu anlamıyorum? Onun kodu sıfır dönüşüm gerektirir.
Johnathon Sullinger

Tüm mantığımı XAML'in ellerine yerleştirmek istemedim. Mantığımı kendi yolumla yapmayı tercih ederim (her zaman en iyi yol değil), ancak plana göre bir şeyin nereye ve neden gitmediğini anlamama izin veriyor. Onun daha az karmaşık, ama mantığı yapmak için XAML / WPF dayanır. Ben sadece bunun hayranı değilim. Bir kediyi cildin 10.000 yolu biliyor musunuz?
Laki Politis

Yeterince adil. Şahsen benim için zaten hazır, kutudan yapılmış özellikleri kullanmayı tercih ediyorum ama bu sadece benim tercihim;) Her biri kendi!
Johnathon Sullinger

Evet efendim! Tamamen anladım. Web geliştirmeden gelen Yazılım geliştirmeye zorlandım. WPF hakkında güncel değilim ve ilerledikçe çok şey öğrenmek zorunda kaldım. WPF / XAML kontrollerinin tüm karmaşıklıklarını hala anlamıyorum ve bu yüzden işlerin nasıl çalışacağını umduğum çözümlerden daha fazla hıçkırık buluyorum. Ama bu sohbeti takdir ediyorum. Biraz daha araştırma yapmamı sağladı.
Laki Politis

0

Nick'in çözümü daha basitleştirilebilir, fantezi bir şey olmadan, sadece tek bir dönüştürücüye ihtiyacınız olacak:

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

Ardından, birleşik giriş kutunuzun görünmesini istediğiniz her yerde bunu kullanırsınız:

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

0

Ben olduğu gibi bu uygulamayı tavsiye etmem ama umarım bu iyi bir çözüm ilham olabilir.

Diyelim ki sıralamanız Foo. Sonra böyle bir şey yapabilirsiniz.

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

Ardından Window.Loadyöntemde, birleşik giriş ObservableCollection<FooViewModel>kutusunun DataContext'i olarak ayarlayabileceğiniz tüm numaralandırmaları yükleyebilirsiniz .


0

Sadece basit tuttum. ViewModel'imde enum değerleri olan öğelerin bir listesini oluşturdum:

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

Benim xaml kodunda sadece buna ihtiyacım var:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
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.