วิธีการผูก enum กับตัวควบคุม combobox ใน WPF?


182

ฉันพยายามหาตัวอย่างง่ายๆที่แสดง enums ตามที่เป็นอยู่ ตัวอย่างทั้งหมดที่ฉันได้เห็นพยายามที่จะเพิ่มสตริงการแสดงที่ดูดี แต่ฉันไม่ต้องการความซับซ้อนนั้น

โดยทั่วไปฉันมีคลาสที่มีคุณสมบัติทั้งหมดที่ฉันผูกโดยตั้งค่า DataContext เป็นคลาสนี้ก่อนจากนั้นระบุการโยงเช่นนี้ในไฟล์ xaml:

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

แต่สิ่งนี้จะไม่แสดงค่า enum ในComboBoxรายการเป็น


9
นี่คือสิ่งที่คุณกำลังมองหา: WPF ObjectDataProvider - Binding Enum to ComboBoxคุณยังสามารถดาวน์โหลดตัวอย่างซอร์สโค้ดได้จากที่นั่น

คำตอบที่ดีที่สุดในความคิดของฉันคือ: stackoverflow.com/questions/58743/…
gimpy

ซ้ำกันที่เป็นไปได้ของการDatabinding คุณสมบัติ enum เพื่อ ComboBox ใน WPF
UuDdLrLrSs

คำตอบ:


306

คุณสามารถทำได้จากรหัสโดยวางรหัสต่อไปนี้ในLoadedตัวจัดการเหตุการณ์Window ตัวอย่างเช่น:

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

หากคุณต้องการผูกไว้ใน XAML คุณต้องใช้ObjectDataProviderเพื่อสร้างวัตถุที่มีอยู่เป็นแหล่งที่มาผูกพัน:

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

ดึงดูดความสนใจในรหัสต่อไป:

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

คำแนะนำเกี่ยวกับวิธีการ map namespace และประกอบคุณสามารถอ่านบนMSDN


1
ตัวอย่างการทดสอบจากลิงค์แรกใช้งานได้ ดูรหัสเพิ่มเติมและแสดงความคิดเห็นในคำตอบของฉัน
Kyrylo M

1
พบปัญหาของคุณในฟอรัม MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ) พยายามทำความสะอาดและสร้างโครงการใหม่ อาจเป็นไปได้ว่าคุณควรถามปัญหานั้นกับคำถามอื่น นี่เป็นสิ่งเดียวที่ฉันสามารถให้คำแนะนำได้ ... อย่างไรก็ตามตัวอย่างที่แสดงนั้นถูกต้อง
Kyrylo M

1
ขอบคุณที่แปลกประหลาด แต่ฉันได้เห็นสิ่งที่คล้ายกันด้วยความบ้าคลั่ง WPF จะทำและแจ้งให้คุณทราบ Btw เป็นปัญหาเดียวกันที่อธิบายไว้ที่นี่: social.msdn.microsoft.com/Forums/en-US/wpf/thread/ …
Joan Venge

2
คุณต้องการเพิ่มการอ้างอิงและเพิ่มxmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"ใน XAML เพื่อใช้งาน นี่คือคำแนะนำ: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M

4
คุณสามารถใช้เครื่องมือเช่น ReSharper แยกวิเคราะห์แอสเซมบลีที่อ้างอิงทั้งหมดและให้คำแนะนำสิ่งที่จำเป็นต้องรวม ไม่จำเป็นต้องเขียน - เพียงแค่เลือกจากตัวเลือก
Kyrylo M

117

ฉันชอบวัตถุทั้งหมดที่ฉันผูกพันที่จะกำหนดไว้ในของฉันViewModelดังนั้นฉันจึงพยายามหลีกเลี่ยงการใช้<ObjectDataProvider>ใน xaml เมื่อเป็นไปได้

โซลูชันของฉันไม่ใช้ข้อมูลที่กำหนดไว้ในมุมมองและไม่ต้องใช้โค้ด เฉพาะ DataBinding, ValueConverter ที่ใช้ซ้ำได้ซึ่งเป็นวิธีการรับชุดคำอธิบายสำหรับประเภท Enum ใด ๆ และคุณสมบัติเดียวใน ViewModel ที่จะเชื่อมโยง

เมื่อฉันต้องการที่จะผูกEnumกับComboBoxข้อความที่ฉันต้องการที่จะแสดงผลไม่ตรงกับค่าของEnumฉันจึงใช้แอตทริบิวต์ที่จะให้มันข้อความที่จริงผมต้องการที่จะเห็นใน[Description()] ComboBoxถ้าฉันมีจำนวนวันในสัปดาห์มันจะออกมาเป็นแบบนี้:

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

ก่อนอื่นฉันสร้างชั้นผู้ช่วยด้วยวิธีการสองสามอย่างเพื่อจัดการกับ enums วิธีหนึ่งได้รับรายละเอียดสำหรับค่าเฉพาะวิธีอื่นได้รับค่าทั้งหมดและคำอธิบายสำหรับประเภท

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

ValueConverterต่อไปเราจะสร้าง การสืบทอดจากMarkupExtensionทำให้ง่ายต่อการใช้ใน XAML ดังนั้นเราไม่จำเป็นต้องประกาศว่าเป็นทรัพยากร

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

ของฉันViewModelต้องการเพียง 1 คุณสมบัติที่ฉันViewสามารถผูกกับทั้งSelectedValueและItemsSourceคอมโบได้:

private DayOfWeek dayOfWeek;

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

และในที่สุดก็ผูกComboBoxมุมมอง (ใช้ValueConverterในการItemsSourceผูก) ...

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

ในการใช้งานโซลูชันนี้คุณจะต้องคัดลอกEnumHelperชั้นเรียนและชั้นเรียนของฉันEnumToCollectionConverterเท่านั้น พวกเขาจะทำงานกับenums ใด ๆ นอกจากนี้ผมไม่ได้รวมไว้ที่นี่ แต่ValueDescriptionชั้นเป็นเพียงระดับที่เรียบง่ายด้วย 2 คุณสมบัติของวัตถุประชาชนคนหนึ่งที่เรียกว่าหนึ่งเรียกว่าValue Descriptionคุณสามารถสร้างด้วยตัวคุณเองหรือคุณสามารถเปลี่ยนรหัสเพื่อใช้Tuple<object, object>หรือKeyValuePair<object, object>


9
เพื่อให้งานนี้ฉันต้องสร้างValueDescriptionชั้นเรียนที่มีสมบัติสาธารณะสำหรับValueและDescription
Perchik

4
ใช่คุณสามารถเปลี่ยนรหัสนี้เพื่อใช้Tuple<T1, T2>หรือKeyValuePair<TKey, TValue>แทนValueDescriptionชั้นเรียนแล้วคุณไม่จำเป็นต้องสร้างของคุณเอง
Nick

ฉันจำเป็นต้องใช้ OnPropertyChanged (หรือเทียบเท่า) สำหรับทั้งคุณสมบัติ ViewModel ไม่ใช่เฉพาะ SelectedClass
จะ

คุณไม่จำเป็นต้องใช้ OnPropertyChanged สำหรับคุณสมบัติที่ส่งคืนรายการ รายการถูกสร้างขึ้นจากค่าใน Enum มันจะไม่เปลี่ยนแปลงในช่วงเวลาทำงานและเมื่อมันไม่เคยเปลี่ยนแปลงมันไม่จำเป็นต้องแจ้งให้ทุกคนที่มีการเปลี่ยนแปลง นอกจากนี้สำหรับรุ่นที่อัปเดตแล้วคุณสมบัติของรายการก็ไม่จำเป็นแม้แต่น้อย
Nick

ItemSource ของ Combobox และ SelectedValue มีคุณสมบัติเหมือนกันอย่างไร ItemsSource ไม่จำเป็นต้องเป็นรายการหรือไม่ โอ้ฉันเข้าใจแล้วมันเป็นสิ่งที่ EnumHelper สร้างรายการของวัตถุ สิ่งนี้ทำให้ ViewModel ของฉันง่ายขึ้นเพราะฉันไม่จำเป็นต้องเก็บรักษารายการวัตถุแยกต่างหากเพื่อเติมข้อมูล ItemSource
Stealth Rabbi

46

ฉันใช้โซลูชันอื่นโดยใช้ MarkupExtension

  1. ฉันทำคลาสซึ่งมีแหล่งรายการ:

    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. เกือบทั้งหมดแล้ว ... ใช้มันใน XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. เปลี่ยน 'enums: States' เป็น enum ของคุณ


1
@Nick: คำตอบที่ได้รับการยอมรับคือการอ้างอิง enum (หรือรุ่นที่คุณพูด) ใน xaml ด้วย โซลูชันของคุณกำลังสร้าง 2 คุณสมบัติและเขตข้อมูลสำรองในรูปแบบมุมมองซึ่งฉันไม่ชอบ (กฎ DRY) และแน่นอนคุณไม่ต้องใช้e.ToString()ชื่อที่แสดง คุณสามารถใช้ตัวแยกวิเคราะห์แอตทริบิวต์ descrtiption ของคุณเองอะไรก็ได้
tom.maruska

2
@ tom.maruska ฉันไม่ได้พยายามหาคำตอบของฉันเทียบกับคำตอบของคุณ แต่เมื่อคุณนำมันขึ้นมาการมี 2 คุณสมบัตินั้นไม่ได้ละเมิดกฎ DRY เมื่อมันเป็น 2 คุณสมบัติที่แตกต่างกันซึ่งตอบสนองวัตถุประสงค์ที่แตกต่างกัน และคำตอบของคุณก็ต้องเพิ่มคุณสมบัติ (คุณเรียกคุณสมบัตินี้ด้วยตัวเอง{Binding Path=WhereEverYouWant}) และถ้าคุณต้องการให้มันรองรับการเชื่อมโยง 2 ทางคุณจะต้องมีเขตข้อมูลสำรองด้วยเช่นกัน ดังนั้นคุณไม่ได้แทนที่ 2 คุณสมบัติและ 1 เขตข้อมูลสำรองโดยทำเช่นนี้คุณเพียงแทนที่คุณสมบัติแบบอ่านอย่างเดียว 1 บรรทัดเท่านั้น
Nick

@ Nick ใช่คุณมีสิทธิเกี่ยวกับข้อมูลสถานที่ให้บริการและการสนับสนุนที่ :)
tom.maruska

24

ใช้ ObjectDataProvider:

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

แล้วผูกกับทรัพยากรคงที่:

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

อ้างอิงจากบทความนี้


4
ทางออกที่สมบูรณ์แบบเรียบง่าย เนมสเปซสำหรับระบบตามคำตอบของ kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite

ทำงานได้ดีในโครงการ WPF ของ Visual Studio 2017
Sorush

10

คำตอบของนิคช่วยฉันได้จริงๆ แต่ฉันรู้ว่ามันอาจถูกปรับแต่งเล็กน้อยเพื่อหลีกเลี่ยงชั้นพิเศษ ValueDescription ฉันจำได้ว่ามีคลาส KeyValuePair อยู่ในกรอบดังนั้นจึงสามารถใช้แทนได้

รหัสเปลี่ยนแปลงเพียงเล็กน้อย:

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

และในที่สุด XAML:

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

ฉันหวังว่านี่จะเป็นประโยชน์กับคนอื่น ๆ


การใช้งานครั้งแรกของฉันใช้KeyValuePairแต่ในท้ายที่สุดฉันตัดสินใจใช้KeyValuePairเพื่อแสดงสิ่งที่ไม่ใช่คู่คีย์ - ค่าเพื่อหลีกเลี่ยงการเขียนคลาสที่เรียบง่ายเล็กน้อยไม่สมเหตุสมผล ValueDescriptionชั้นเพียง 5 เส้นและ 2 ของพวกเขาเป็นเพียง{และ}
นิค

8

คุณจะต้องสร้างอาร์เรย์ของค่าใน enum ซึ่งสามารถสร้างได้โดยการเรียกSystem.Enum.GetValues ​​()ผ่านTypeค่า enum ที่คุณต้องการให้รายการ

หากคุณระบุสิ่งนี้ไว้สำหรับItemsSourceคุณสมบัติก็ควรบรรจุด้วยค่า enum ทั้งหมด คุณอาจต้องการที่จะผูกSelectedItemไปEffectStyle(สมมติว่ามันเป็นทรัพย์สินของ enum เดียวกันและมีมูลค่าปัจจุบัน)


ขอบคุณคุณช่วยแสดงส่วนแรกในรหัสได้ไหม ฉันไม่แน่ใจว่าจะเก็บค่า enum เป็นอาร์เรย์ได้ที่ไหน คุณสมบัติ enum ตั้งอยู่ในคลาสอื่น ฉันสามารถทำขั้นตอน GetValues ​​ภายใน xaml ได้หรือไม่
Joan Venge

4

โพสต์ทั้งหมดข้างต้นพลาดเคล็ดลับง่ายๆ เป็นไปได้จากการเชื่อมโยงของ SelectedValue เพื่อค้นหาวิธีเติมข้อมูล ItemsSource โดยอัตโนมัติเพื่อให้มาร์กอัป XAML ของคุณเป็นเพียง

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

ตัวอย่างเช่นใน ViewModel ของฉันฉันมี

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


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

ValidateRaiseAndSetIfChanged เป็นเบ็ด INPC ของฉัน ของคุณอาจแตกต่างกัน

การใช้ EnumComboBox มีดังต่อไปนี้ แต่ก่อนอื่นฉันต้องมีผู้ช่วยเล็กน้อยเพื่อรับสตริงและค่าการแจงนับของฉัน

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

และคลาสหลัก (หมายเหตุฉันใช้ ReactiveUI สำหรับการเปลี่ยนแปลงคุณสมบัติของ hook ผ่านทาง 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";

        }
    }
}

คุณต้องตั้งค่าสไตล์ให้ถูกต้องใน Generic.XAML ไม่เช่นนั้นกล่องของคุณจะไม่แสดงผลอะไรและคุณจะดึงผมออกมา

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

และนั่นก็คือ เห็นได้ชัดว่าสามารถขยายเพื่อรองรับ i18n แต่จะทำให้การโพสต์อีกต่อไป


3

แอปสากลดูเหมือนจะทำงานแตกต่างกันเล็กน้อย มันไม่มีพลังทั้งหมดของ XAML ที่มีคุณสมบัติครบถ้วน สิ่งที่ได้ผลสำหรับฉันคือ:

  1. ฉันสร้างรายการของค่า enum เป็น enums (ไม่แปลงเป็นสตริงหรือจำนวนเต็ม) และผูก ComboBox ItemsSource ไว้ที่
  2. จากนั้นฉันสามารถผูก ComboBox ItemSelected กับทรัพย์สินสาธารณะของฉันซึ่งเป็นประเภท enum ในคำถาม

เพียงเพื่อความสนุกสนานผมวิปปิ้งขึ้นเล็กน้อยระดับเทมเพลตเพื่อช่วยในการนี้และเผยแพร่ไปยังหน้าตัวอย่าง MSDN บิตพิเศษให้ฉันเลือกแทนที่ชื่อของ enums และให้ฉันซ่อนบาง enums รหัสของฉันดูแย่มากเหมือนของ Nick (ด้านบน) ซึ่งฉันหวังว่าฉันจะได้เห็นก่อนหน้านี้

ใช้ตัวอย่าง;  มันรวมการผูก twoway หลายรายการกับ enum


3

มีคำตอบที่ยอดเยี่ยมมากมายสำหรับคำถามนี้และฉันส่งของฉันอย่างนอบน้อม ฉันพบว่าของฉันค่อนข้างง่ายและสง่างามกว่า มันต้องการเพียงตัวแปลงค่า

รับ 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
}

และตัวแปลงค่า ...

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

ทรัพยากร ...

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

ประกาศ XAML ...

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

ดูโมเดล ...

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

ส่งผลให้ combobox ...

ComboBox ผูกไว้กับ enum


สำหรับฉันนี่เป็นคำตอบที่ดีที่สุดสำหรับคำถาม: เรียบง่ายเข้าใจง่ายตรงไปตรงมาเพื่อนำไปใช้
Informagic

ปัญหาเกี่ยวกับการแก้ปัญหานี้คือมันไม่สามารถคำนวณได้
Robin Davies

@RobinDavies คุณสามารถ จำกัด วงได้ ต้องใช้คำอธิบายที่กำหนดเองคุณสมบัติที่ฉันได้สร้างไม่กี่ ดูคำถาม SO นี้สำหรับแนวคิดบางอย่าง: 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));
    }
}

คุณควรขยายคำตอบของ Rogers และ Greg ด้วยตัวแปลงค่า Enum ชนิดนี้หากคุณมีผลผูกพันโดยตรงกับคุณสมบัติของโมเดลวัตถุ enum


1

หากคุณมีผลผูกพันกับคุณสมบัติ enum จริงใน ViewModel ของคุณไม่ใช่ตัวแทน int ของ enum สิ่งต่าง ๆ จะยุ่งยาก ฉันพบว่าจำเป็นต้องเชื่อมโยงกับการแสดงสตริงไม่ใช่ค่า int ตามที่คาดไว้ในตัวอย่างข้างต้นทั้งหมด

คุณสามารถบอกได้ว่าเป็นกรณีนี้หรือไม่โดยการผูกกล่องข้อความธรรมดากับคุณสมบัติที่คุณต้องการผูกไว้กับ ViewModel ของคุณ ถ้ามันแสดงข้อความให้ผูกกับสตริง หากมันแสดงตัวเลขให้ผูกกับค่า หมายเหตุฉันใช้จอแสดงผลสองครั้งซึ่งปกติแล้วจะเป็นข้อผิดพลาด แต่เป็นวิธีเดียวที่ใช้งานได้

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

เกร็ก


คำตอบนี้ดูเหมือนจะไม่สมบูรณ์: * อะไรคือ / core /?
trapicki

1

ฉันชอบคำตอบของ tom.maruskaแต่ฉันต้องการสนับสนุนประเภท enum ใด ๆ ที่แม่แบบของฉันอาจพบในขณะทำงาน สำหรับสิ่งนั้นฉันต้องใช้การเชื่อมเพื่อระบุชนิดของส่วนขยายของมาร์กอัป ฉันสามารถทำงานในคำตอบนี้จาก nicolay.anykienko เพื่อสร้างส่วนขยายมาร์กอัปที่ยืดหยุ่นมากซึ่งจะทำงานได้ทุกกรณีที่ฉันนึกออก มันถูกบริโภคแบบนี้:

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

แหล่งที่มาสำหรับส่วนขยายมาร์กอัปที่บดแล้วที่อ้างอิงข้างต้น:

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

คำอธิบายที่ง่ายและชัดเจน: 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

ใช้ReactiveUIฉันสร้างโซลูชันสำรองต่อไปนี้ มันไม่ใช่วิธีการแก้ปัญหาแบบ all-in-one ที่สง่างาม แต่ฉันคิดว่าอย่างน้อยที่สุดก็สามารถอ่านได้

ในกรณีของฉันการผูกรายการenumไปยังตัวควบคุมเป็นกรณีที่ไม่ค่อยเกิดขึ้นดังนั้นฉันไม่จำเป็นต้องขยายโซลูชันข้ามฐานรหัส แต่รหัสที่สามารถทำทั่วไปมากขึ้นโดยการเปลี่ยนเป็นEffectStyleLookup.Item Objectฉันทดสอบด้วยโค้ดของฉันไม่จำเป็นต้องทำการดัดแปลงอื่น ๆ ซึ่งหมายความว่าสามารถใช้คลาสตัวช่วยเดียวกับenumรายการใดก็ได้ แม้ว่ามันจะลดความสามารถในการอ่านReactiveList<EnumLookupHelper>แต่ก็ไม่มีเสียงเรียกเข้าที่ดี

การใช้คลาสตัวช่วยต่อไปนี้:

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

ใน ViewModel ให้แปลงรายการ enums และแสดงเป็นคุณสมบัติ:

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

ในการComboBoxใช้ประโยชน์จากSelectedValuePathทรัพย์สินเพื่อผูกกับenumค่าเดิม:

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

ในมุมมองสิ่งนี้ช่วยให้เราสามารถเชื่อมโยงต้นฉบับenumเข้ากับSelectedEffectStyleใน ViewModel แต่แสดงToString()ค่าใน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) );
});

ฉันคิดว่า ViewModel ของคุณมีข้อผิดพลาด 1) ไม่ควรเป็น ReactiveList ของ EffectStyleLookup หรือไม่ 2) คุณควรสร้าง ReactiveList <T> () ก่อน จากนั้นเพิ่มรายการ สุดท้าย: ReactiveList <T> เลิกใช้แล้ว (แต่ยังใช้งานได้) EffectStyles = ใหม่ ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (รายการ); ขอบคุณที่สละเวลาแสดงสิ่งนี้
user1040323

0

ฉันกำลังเพิ่มความคิดเห็นของฉัน (ใน VB เศร้า แต่แนวคิดที่สามารถจำลองได้ง่ายไปยัง C # ในการเต้นของหัวใจ) เพราะฉันต้องอ้างอิงนี้และไม่ชอบคำตอบใด ๆ เพราะพวกเขาซับซ้อนเกินไป มันไม่ควรที่จะยากขนาดนี้

ดังนั้นฉันจึงคิดวิธีที่ง่ายขึ้น ผูก Enumerators เข้ากับพจนานุกรม ผูกพจนานุกรมนั้นกับ Combobox

คอมโบของฉัน:

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

รหัสของฉันที่อยู่เบื้องหลัง หวังว่านี่จะช่วยคนอื่นได้

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 นั้นง่ายกว่าของคุณมาก - ฉันไม่เข้าใจว่ามันซับซ้อนแค่ไหน? เขาต้องการแปลงเป็นศูนย์ในรหัส
Johnathon Sullinger

ฉันไม่ต้องการวางตรรกะทั้งหมดของฉันไว้ในมือของ XAML ฉันชอบทำตามวิธีการของฉันเอง (ไม่ใช่วิธีที่ดีที่สุดเสมอไป) แต่มันช่วยให้ฉันเข้าใจว่าที่ไหนและทำไมบางสิ่งบางอย่างไม่เป็นไปตามแผนที่วางไว้ เขามีความซับซ้อนน้อยกว่า แต่อาศัย XAML / WPF เพื่อทำตรรกะ ฉันแค่ไม่ใช่แฟนของเรื่องนั้น 10,000 วิธีในการสกินแมวคุณรู้หรือไม่?
Laki Politis

ยุติธรรมพอสมควร โดยส่วนตัวฉันชอบที่จะใช้ฟีเจอร์ต่าง ๆ ที่สร้างขึ้นแล้วนอกกรอบสำหรับฉัน แต่นั่นเป็นเพียงความชอบของฉัน;) สำหรับแต่ละตัวมี!
Johnathon Sullinger

ครับท่าน! ฉันเข้าใจอย่างถ่องแท้ ฉันถูกบังคับให้พัฒนาซอฟต์แวร์ที่มาจากการพัฒนาเว็บ ฉันยังไม่ทันสมัยกับ WPF และต้องเรียนรู้อย่างมากเหมือนที่เคยทำมา ฉันยังไม่เข้าใจความซับซ้อนทั้งหมดของการควบคุม WPF / XAML และดังนั้นฉันจึงค้นหาสะอึกมากกว่าโซลูชันในวิธีที่ฉันคาดหวังสิ่งที่ทำงาน แต่ฉันขอขอบคุณการสนทนานี้ มันทำให้ฉันทำวิจัยเพิ่มเติม
Laki Politis

0

การแก้ปัญหาของ Nick สามารถทำให้ง่ายขึ้นได้มากขึ้นโดยที่ไม่มีอะไรแฟนซีคุณจะต้องใช้ตัวแปลงเดียว:

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

จากนั้นคุณใช้สิ่งนี้ทุกที่ที่คุณต้องการให้กล่องคำสั่งผสมของคุณปรากฏ:

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

0

ฉันไม่แนะนำให้ใช้สิ่งนี้เพราะมันเป็น แต่หวังว่ามันจะเป็นแรงบันดาลใจให้กับทางออกที่ดี

สมมติว่า enum ของคุณคือ Foo จากนั้นคุณสามารถทำอะไรเช่นนี้

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

จากนั้นในWindow.Loadวิธีการที่คุณสามารถโหลด enums ทั้งหมดObservableCollection<FooViewModel>ที่คุณสามารถตั้งค่าเป็น DataContext ของคอมโบ


0

ฉันแค่ทำให้มันง่าย ฉันสร้างรายการด้วยค่า enum ใน ViewModel ของฉัน:

public enum InputsOutputsBoth
{
    Inputs,
    Outputs,
    Both
}

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

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

private InputsOutputsBoth _selectedIoType;

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

ในรหัส xaml ของฉันฉันต้องการสิ่งนี้:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.