Как привязать перечисление к элементу управления списком в WPF?

182

Я пытаюсь найти простой пример, где перечисления отображаются как есть. Все примеры, которые я видел, пытаются добавить красивые строки отображения, но я не хочу такой сложности.

По сути, у меня есть класс, который содержит все свойства, которые я связываю, сначала установив DataContext для этого класса, а затем указав привязку, подобную этой, в файле xaml:

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

Но это не показывает значения перечисления в ComboBoxэлементах as.

Джоан Венге
источник
9
Вот что вы ищете: WPF ObjectDataProvider - Привязка Enum к ComboBox Вы также можете скачать полный пример исходного кода оттуда.
Лучший ответ, по моему мнению, находится в: stackoverflow.com/questions/58743/…
gimpy
Возможный дубликат Databinding привязки свойства enum к ComboBox в WPF
UuDdLrLrSs

Ответы:

307

Вы можете сделать это из кода, поместив следующий код в 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
Протестированный пример по первой ссылке, работает нормально. Смотрите добавленный код и комментарий в моем ответе.
Kyrylo M
1
Обнаружил вашу проблему на форумах MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/… ). Попробуйте почистить и перестроить проект. Вероятно, вы должны задать эту проблему здесь по другому вопросу. Это единственное, что я могу посоветовать ... Во всяком случае, показанный пример правильный.
Kyrylo M
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. Он анализирует все ссылочные сборки и дает предложения, что нужно включить. Не нужно писать - просто выберите один из вариантов.
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,
  ...
}

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

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класс. Они будут работать с любыми перечислениями. Кроме того, я не включил его здесь, но 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. Он никогда не изменится во время выполнения, и когда он никогда не изменится, ему никогда не нужно будет уведомлять кого-либо о том, что он изменился. Кроме того, в обновленной версии свойство list вообще не требуется.
Ник
Каким образом ItemSource и SelectedValue комбинированного списка имеют одно и то же свойство. Разве ItemsSource не должен быть списком? О, я вижу, это потому что 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. Замените enum: States на свой enum

tom.maruska
источник
1
@Nick: Принятый ответ ссылается на enum (или модель, как вы сказали) в xaml. Ваше решение - создать 2 свойства и поле поддержки в модели представления, что мне не понравилось (правило СУХОЙ). И, конечно, вам не нужно использовать e.ToString()для отображения имени. Вы можете использовать свой собственный переводчик, анализатор атрибутов descrtiption, что угодно.
Tom.maruska
2
@ tom.maruska Я не пытаюсь получить ответ против вашего ответа, но поскольку вы подняли его, наличие двух свойств не нарушает правило СУХОЙ, когда они представляют собой два разных свойства, которые служат разным целям. И ваш ответ также потребует добавления свойства (вы даже вызывали это свойство самостоятельно {Binding Path=WhereEverYouWant}), и если вы хотите, чтобы оно поддерживало двустороннее связывание, у вас также будет поле для поддержки. Таким образом, вы не заменяете 2 свойства и 1 вспомогательное поле, а только 1 однострочное свойство только для чтения.
Ник
@ Ник Да, вы правы насчет этой собственности и
вспомогательного
25

Используйте 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
Идеально простое решение. Пространство имен для System, как в ответе Кирмира:xmlns:System="clr-namespace:System;assembly=mscorlib"
Джонатан Твайт
Хорошо работает в проектах WPF в Visual Studio 2017.
Sorūsh
11

Ответ Ника действительно помог мне, но я понял, что его можно немного подправить, чтобы избежать дополнительного класса 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

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

Если вы укажете это для ItemsSourceсвойства, то оно должно быть заполнено всеми значениями перечисления. Вы, вероятно, хотите связать 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, но сделает этот пост длиннее.

bradgonesurfing
источник
3

Универсальные приложения работают немного по-другому; он не обладает всей мощью полнофункционального XAML. Что сработало для меня:

  1. Я создал список значений перечисления в виде перечислений (не преобразованных в строки или целые числа) и привязал ComSBox ItemsSource к этому
  2. Затем я могу привязать ComboBox ItemSelected к общедоступному свойству, тип которого - рассматриваемый enum.

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

Запуск образца;  он включает в себя несколько привязок к перечислению

PESMITH_MSFT
источник
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 связан с перечислением

AQuirky
источник
Для меня это лучшее решение вопроса: просто, легко понять, легко реализовать.
Informagic
Проблема этого решения в том, что оно не локализуемо.
Робин Дэвис
@RobinDavies вы можете локализовать его. Требуется пользовательский DescriptionAttribute, который я построил несколько. Посмотрите этот ТАК вопрос для некоторых идей: 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. Если он показывает текст, привязать к строке. Если он показывает число, привязать к значению. Заметьте, я использовал Display дважды, что обычно было бы ошибкой, но это единственный способ, которым он работает.

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

Greg

Грег Гам
источник
Этот ответ кажется неполным: * Что такое / 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>
JLo-Gmail
источник
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к 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) );
});
Mitkins
источник
Я думаю, что ваша ViewModel имеет ошибку. 1) Разве это не должен быть ReactiveList из EffectStyleLookup ?, 2) Сначала вы должны создать пустой ReactiveList <T> (). Затем добавьте предметы. Наконец: ReactiveList <T> теперь устарел (но все еще работает). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (список); Спасибо, что нашли время, чтобы показать это.
user1040323
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. Я просто не фанат этого. Знаете, 10000 способов снять шкуру с кошки?
Лаки Политис
Справедливо. Лично я предпочитаю использовать уже встроенные функции для меня, но это только мое предпочтение;) Каждому есть свои!
Джонатон Саллингер
Да сэр! Я полностью понимаю. Я был вынужден в разработке программного обеспечения, приходящего от веб-разработки. Я не был в курсе 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

Я просто держал это простым. Я создал список элементов со значениями перечисления в моей 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}">
Tsjakka
источник