Як прив’язати перерахунок до елемента управління комбінацією у WPF?


182

Я намагаюся знайти простий приклад, де переліки показані як є. Усі приклади, які я бачив, намагаються додати приємно виглядають рядки дисплея, але я не хочу цього складності.

В основному у мене є клас, який містить усі властивості, які я пов'язую, спочатку встановивши DataContext до цього класу, а потім вказавши прив'язку, як це у файлі xaml:

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

Але це не показує значення перерахунків у ComboBoxелементах "as".


9
Ось що ви шукаєте: WPF ObjectDataProvider - Прив’язування Enum до ComboBox Ви також можете завантажити повний приклад вихідного коду звідти.

Найкраща відповідь, на мій погляд, знаходиться у: stackoverflow.com/questions/58743/…
gimpy

Можливий дублікат даних, що прив'язує властивість 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"

Посібник, як зіставити простір імен та збірку, можна прочитати на MSDN .


1
Випробуваний приклад з першого посилання, працює добре. Дивіться доданий код та коментар у моїй відповіді.
Кирило М

1
Знайшли свою проблему на форумах MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Спробуйте очистити та відновити проект. Напевно, вам слід поставити цю проблему тут з іншого питання. Це єдине, що я можу порадити ... У будь-якому випадку показаний приклад є правильним.
Кирило М

1
Дякую, це химерно, але я бачив подібні речі з wpf божевіллям. Зробимо і дам вам знати. До речі, це та сама проблема, описана тут: social.msdn.microsoft.com/Forums/en-US/wpf/thread/…
Джоан

2
Вам потрібно додати посилання на нього та додати xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"в XAML, щоб використовувати його. Ось довідник: msdn.microsoft.com/en-us/library/ms747086.aspx
Kyrylo M

4
Можна використовувати такі інструменти, як ReSharper. Він аналізує всі посилаються на збори та дає пропозиції, що потрібно включити. Писати не потрібно - просто виберіть із варіантів.
Кирило М

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

Спочатку я створив хелперний клас з парою методів боротьби із суперечками. Один метод отримує опис конкретного значення, інший отримує всі значення та їх описи для типу.

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потрібна лише одна властивість, з якою мої 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клас. Вони працюватимуть з будь-якими перерахунками. Також я сюди не включив її, але ValueDescriptionклас - це просто простий клас із двома властивостями загальнодоступних об'єктів, одним називаним Value, одним викликаним Description. Ви можете створити це самостійно або ви можете змінити код, щоб використовувати Tuple<object, object>абоKeyValuePair<object, object>


9
Для того, щоб зробити цю роботу, я повинен був створити ValueDescriptionклас , який має спільні властивості для ValueіDescription
Перчик

4
Так, ви можете також змінити цей код, щоб використовувати клас Tuple<T1, T2>або KeyValuePair<TKey, TValue>замість ValueDescriptionкласу, і тоді вам не доведеться створювати свій власний.
Нік

Мені потрібно було реалізувати OnPropertyChanged (або еквівалент) для обох властивостей ViewModel, а не лише SelectedClass.
Буде

Вам не потрібно буде впроваджувати OnPropertyChanged для властивості, яка повертає список. Список генерується зі значень в Enum. Він ніколи не зміниться під час запуску, і коли він ніколи не зміниться, йому ніколи не потрібно повідомляти когось про те, що він змінився. Крім того, в оновленій версії властивість списку навіть не потрібна.
Нік

Яким чином властивість ItemSource та SelectedValue у спільному вікні є одним і тим же властивістю. Чи не потрібно ItemSource бути списком? О, я бачу, тому що EnumHelper складає список об'єктів. це фактично робить мій ViewModel простішим, оскільки мені не потрібно підтримувати окремий список об'єктів, щоб заповнити ItemSource.
Стелс-рабин

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" на ваш перелік


1
@ Nick: Прийнята відповідь також посилається на enum (або модель, як ви сказали) в xaml. Ваше рішення - створення 2 властивостей та резервного поля у моделі перегляду, що мені не сподобалось (правило DRY). І звичайно, вам не доведеться використовувати e.ToString()для відображення ім'я. Ви можете використовувати власний перекладач, аналізатор атрибутів дескрипції, будь-який інший.
tom.maruska

2
@ tom.maruska Я не намагаюся вникати у свою відповідь проти вашої відповіді, але оскільки ви її вигадали, наявність 2 властивостей не порушує правила DRY, коли вони є двома різними властивостями, які служать різним цілям. І ваша відповідь також вимагатиме додати властивість (ви навіть самі називали цю властивість {Binding Path=WhereEverYouWant}), і якщо ви хочете, щоб вона підтримувала двостороння прив'язка, у вас також буде поле резервного копіювання. Отже, виконуючи це, ви не замінюєте 2 властивості та 1 резервне поле, ви замінюєте лише 1 однолінійне властивість лише для читання.
Нік

@Nick Так, ви маєте рацію щодо цього майнового та
захисного

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.
Соруш,

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але, врешті-решт, я вирішив використати a, KeyValuePairщоб представити щось, що не є ключовим значенням, аби лише написати тривіально простий клас, не мав сенсу. У ValueDescriptionкласі всього 5 рядків, а 2 з них якраз {і}
Нік

8

Вам потрібно буде створити масив значень в enum, який можна створити, зателефонувавши до System.Enum.GetValues ​​() , передавши йому Typeчисло перерахунку, для якого потрібні елементи.

Якщо вказати це для ItemsSourceвластивості, то воно повинно бути заповнене усіма значеннями enum. Ви , ймовірно , хочете зв'язати SelectedItemз EffectStyle(за умови , що це властивість того ж перерахування і містить поточне значення).


Дякую, чи можете ви, будь ласка, показати першу частину коду? Я не впевнений, де зберігати значення перерахування як масив? Властивість enum розташована в іншому класі. Чи можу я зробити цей крок GetValues ​​всередині xaml?
Джоан Венге

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 для підключення змін властивостей через 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 до мого загальнодоступного майна, типом якого є переслідувана суть

Тільки для розваги я вивів трохи шаблонований клас, щоб допомогти з цим, і опублікував його на сторінках зразків MSDN . Додаткові біти дозволяють мені заздалегідь змінювати назви переліків і дозволяти мені приховати деякі перелічення. Мій код виглядає так жахливо, як Нік (вище), якого б я хотів бачити раніше.

Запуск зразка;  він включає кілька двосторонніх прив’язок до перерахунку


3

На це питання є багато відмінних відповідей, і я покірно подаю свою. Я вважаю, що моя дещо простіша і елегантніша. Для цього потрібен лише перетворювач значень.

З урахуванням перерахунку ...

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 пов'язаний з перерахунком


Для мене це найкраще рішення питання: простий, зрозумілий, простий у здійсненні.
Інформагічний

Проблема цього рішення полягає в тому, що він нерозбірливий.
Робін Девіс

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

Ви повинні розширити відповідь Роджерса та Грега таким видом перетворювача значень 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 , але мені потрібно було підтримати будь-який тип перерахунку, з яким мій шаблон може виникати під час виконання. Для цього мені довелося використовувати прив'язку, щоб вказати тип розширення розмітки. Мені вдалося попрацювати над цією відповіддю від 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, я створив таке альтернативне рішення. Це не елегантне рішення "все в одному", але я думаю, що він принаймні читабельний.

У моєму випадку прив'язка списку enumдо елемента контролю є рідкісним випадком, тому мені не потрібно масштабувати рішення на основі кодової бази. Однак код можна зробити більш загальним, змінивши EffectStyleLookup.Itemна Object. Я перевірив його за допомогою свого коду, ніяких інших модифікацій не потрібно. Що означає, що один клас помічників може бути застосований до будь-якого enumсписку. Хоча це зменшило б його читабельність - ReactiveList<EnumLookupHelper>не має великого кільця для цього.

Використання наступного допоміжного класу:

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

У ViewModel перетворіть список переліків і викрийте його як властивість:

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до SelectedEffectStyleViewModel, але відобразити 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 of EffectStyleLookup? 2) Спершу слід створити порожній ReactiveList <T> (). Потім додайте елементи. Нарешті: ReactiveList <T> тепер застарілий (але все ще працює). EffectStyles = новий ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (список); Дякуємо, що знайшли час, щоб показати це.
користувач1040323

0

Я додаю свій коментар (в VB, на жаль, але концепцію можна легко повторити на C # у серцебитті), тому що мені просто довелося посилатися на це і мені не сподобалася жодна відповідь, оскільки вони були надто складними. Це не повинно бути таким важким.

Тож я придумав простіший спосіб. Прив’яжіть перелічувачі до словника. Прив’яжіть цей словник до 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

Відповідь Кирило набагато простіша за вашу - я не розумію, що тут складне? Його потрібно нульове перетворення в код.
Джоннатан Суллінгер

Я не хотів передавати всю свою логіку в руки XAML. Я вважаю за краще робити свою логіку своїм способом (не завжди найкращим способом), але це дозволяє мені зрозуміти, де і чому щось не йде за планом. Його менш складне, але він покладається на XAML / WPF, щоб зробити логіку. Я просто не фанат цього. Ви знаєте, 10 000 способів шкіри кота?
Laki Politis

Досить справедливо. Я особисто вважаю за краще використовувати для мене вже вбудовані функції, але це лише мої переваги;) Для кожного з них є свої!
Джоннатан Суллінгер

Так, сер! Я цілком розумію. Мене змусили розробляти програмне забезпечення, що випливає з веб-розробки. Я не був настільки сучасним на WPF, і мені доводилося багато вчитися, коли я йшов разом. Я досі не розумію всіх тонкощів елементів керування WPF / XAML, і тому я знаходжу більше гикавки, ніж рішень у тому, як би я очікувала на роботу. Але я ціную цю розмову. Це змусило мене ще кілька досліджень.
Лакі Політ

0

Розв’язання Ніка можна спростити більше, без нічого фантазії, вам знадобиться лише один конвертер:

[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

Я б не рекомендував реалізувати це так, як є, але, сподіваємось, це може надихнути на гарне рішення.

Скажімо, ваш перерахунок - 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методом ви можете завантажити всі перерахунки, 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}">
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.