Как привязать RadioButtons к перечислению?

У меня есть такое перечисление:

public enum MyLovelyEnum
{
    FirstSelection,
    TheOtherSelection,
    YetAnotherOne
};

У меня есть свойство в моем DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }

И в моем клиенте WPF есть три RadioButton.

<RadioButton Margin = "3">First Selection</RadioButton>
<RadioButton Margin = "3">The Other Selection</RadioButton>
<RadioButton Margin = "3">Yet Another one</RadioButton>

Как теперь привязать RadioButtons к свойству для правильной двусторонней привязки?

Если вы хотите сделать это без указания отдельных RadioButton в своем XAML, я бы порекомендовал ListBox, привязанный к значениям перечисления, таким как это или это, и в котором шаблон элемента перезаписан для использования RadioButtons, например это.

Rachel 19.06.2015 23:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
424
1
143 598
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Я бы использовал RadioButtons в ListBox, а затем привязал бы к SelectedValue.

Это более старая ветка по этой теме, но основная идея должна быть такой же: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/

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

Bryan Anderson 02.01.2009 18:29

Этот баг: geekswithblogs.net/claraoscura/archive/2008/10/17/125901.asp‌ x испортил мне день.

Slampen 03.06.2009 15:46

Это, безусловно, лучшее решение, все остальное вызывает избыточный код. (Другой пример использования ListBox)

H.B. 11.05.2012 03:30
Ответ принят как подходящий

Вы можете использовать более общий конвертер

public class EnumBooleanConverter : IValueConverter
{
  #region IValueConverter Members
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    string parameterString = parameter as string;
    if (parameterString == null)
      return DependencyProperty.UnsetValue;

    if (Enum.IsDefined(value.GetType(), value) == false)
      return DependencyProperty.UnsetValue;

    object parameterValue = Enum.Parse(value.GetType(), parameterString);

    return parameterValue.Equals(value);
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    string parameterString = parameter as string;
    if (parameterString == null)
        return DependencyProperty.UnsetValue;

    return Enum.Parse(targetType, parameterString);
  }
  #endregion
}

И в XAML-части вы используете:

<Grid>
    <Grid.Resources>
      <l:EnumBooleanConverter x:Key = "enumBooleanConverter" />
    </Grid.Resources>
    <StackPanel >
      <RadioButton IsChecked = "{Binding Path=VeryLovelyEnum, Converter = {StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
      <RadioButton IsChecked = "{Binding Path=VeryLovelyEnum, Converter = {StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
      <RadioButton IsChecked = "{Binding Path=VeryLovelyEnum, Converter = {StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
    </StackPanel>
</Grid>

Сработало для меня как шарм. В качестве дополнения я модифицировал ConvertBack, чтобы он также возвращал UnsetValue на «false», потому что silverlight (и, предположительно, собственно WPF) вызывает конвертер дважды - один раз при отключении старого значения переключателя и еще раз для установки нового. Я вешал другие вещи на установщик свойств, поэтому я хотел, чтобы он вызывал только один раз. - if (parameterString == null || value.Equals (false)) return DependencyProperty.UnsetValue;

MarcE 14.02.2010 17:44

Насколько я могу судить, этот должен должен выполняться, если только радиокнопки не находятся в разных группах (и кнопки AFAIK без набора GroupName, у которых есть один и тот же родительский элемент, по умолчанию находятся в одной и той же группе). В противном случае вызовы установки свойства «bounce» приведут к странному поведению.

nlawalker 28.05.2010 00:15

да, но если вы вызываете Unset в конвертере при установке значения false, то это не настоящий EnumToBooleanConverter, а скорее EnumToRadioButtonConverter. Поэтому вместо этого я проверяю, отличается ли значение в моем установщике свойств: if (_myEnumBackingField == value) return;

Stéphane 24.03.2011 15:55

Привязка этого решения работает правильно только в одном направлении. Я не мог программно переключить радиокнопку, присвоив привязанному свойству другое значение. Если вам нужно правильно работающее И лучшее решение, используйте подход Скотта.

l46kok 16.10.2012 04:28

@Marc, разве в этом случае не следует возвращать Binding.DoNothing, а не DependencyProperty.UnsetValue?

Mark A. Donohoe 02.01.2013 21:05

@MarquelV - Возможно, но в то время, когда я написал этот комментарий, я работал с Silverlight, у которого не было этой опции. Только что проверил документацию и похоже, что ее все еще нет :-(

MarcE 07.01.2013 14:22

Привилегия «видеть подсчет голосов», которую вы получаете при 1000 репутации, является убедительным доказательством того, что «ненавистники будут ненавидеть».

Millie Smith 11.07.2014 23:36

Повторение вышеупомянутого примечания: ЭТО НЕ РАБОТАЕТ ДВОЙНЫМ ОБРАЗОМ. Вы не можете установить свойство и правильно изменить IsChecked радиокнопки. Вы действительно хотите использовать метод Скотта, представленный ниже.

Clint StLaurent 27.01.2015 23:42

Я второй @ClintStLaurent. Это неправильный ответ, потому что он неправильно выполняет двустороннюю привязку. Ответ Скотта имеет значение.

Matthias Wolf 22.03.2015 17:54

Насколько я могу судить, делает работает правильно в двустороннем режиме, если вы примените исправление MarcE.

stijn 23.08.2015 15:31

Что такое я в <l: EnumBooleanConverter x: Key = "enumBooleanConverter" /> @Lars. Вы можете объяснить, я не знаю, что это такое.

Başar Kaya 09.02.2018 22:11

Чтобы использовать конвертер, вы должны добавить его пространство имен с тегом xmlns в свой xaml. См., Например, stackoverflow.com/questions/5399997/xaml-what-is-local

Lars 11.02.2018 13:46

Вы можете еще больше упростить принятый ответ. Вместо того, чтобы вводить перечисления в виде строк в xaml и выполнять больше работы в вашем конвертере, чем необходимо, вы можете явно передать значение перечисления вместо строкового представления, и, как прокомментировал CrimsonX, ошибки возникают во время компиляции, а не во время выполнения:

ConverterParameter = {x: Статический локальный: YourEnumType.Enum1}

<StackPanel>
    <StackPanel.Resources>          
        <local:ComparisonConverter x:Key = "ComparisonConverter" />          
    </StackPanel.Resources>
    <RadioButton IsChecked = "{Binding Path=YourEnumProperty, Converter = {StaticResource ComparisonConverter}, ConverterParameter = {x:Static local:YourEnumType.Enum1}}" />
    <RadioButton IsChecked = "{Binding Path=YourEnumProperty, Converter = {StaticResource ComparisonConverter}, ConverterParameter = {x:Static local:YourEnumType.Enum2}}" />
</StackPanel>

Затем упростите конвертер:

public class ComparisonConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value?.Equals(parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value?.Equals(true) == true ? parameter : Binding.DoNothing;
    }
}

Изменить (16 декабря 2010 г.):

Thanks to anon for suggesting returning Binding.DoNothing rather than DependencyProperty.UnsetValue.

Примечание. Несколько групп RadioButton в одном контейнере (17 февраля 2011 г.):

In xaml, if radio buttons share the same parent container, then selecting one will de-select all other's within that container (even if they are bound to a different property). So try to keep your RadioButton's that are bound to a common property grouped together in their own container like a stack panel. In cases where your related RadioButtons cannot share a single parent container, then set the GroupName property of each RadioButton to a common value to logically group them.

Изменить (5 апреля 2011 г.):

Simplified ConvertBack's if-else to use a Ternary Operator.

Примечание. Тип Enum, вложенный в класс (28 апреля 2011 г.):

If your enum type is nested in a class (rather than directly in the namespace), you might be able to use the '+' syntax to access the enum in XAML as stated in a (not marked) answer to the question Невозможно найти тип перечисления для статической ссылки в WPF:

ConverterParameter = {x: Статический локальный: YourClass +YourNestedEnumType.Enum1}

Однако из-за этого Проблема с Microsoft Connect конструктор в VS2010 больше не будет загружать указание "Type 'local:YourClass+YourNestedEnumType' was not found.", но проект компилируется и запускается успешно. Конечно, вы можете избежать этой проблемы, если сможете напрямую переместить свой тип перечисления в пространство имен.


Изменить (27 января 2012 г.):

If using Enum flags, the converter would be as follows:
public class EnumToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((Enum)value).HasFlag((Enum)parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value.Equals(true) ? parameter : Binding.DoNothing;
    }
}

Изменить (7 мая 2015 г.):

In case of a Nullable Enum (that is нет asked in the question, but can be needed in some cases, e.g. ORM returning null from DB or whenever it might make sense that in the program logic the value is not provided), remember to add an initial null check in the Convert Method and return the appropriate bool value, that is typically false (if you don't want any radio button selected), like below:
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) {
            return false; // or return parameter.Equals(YourEnumType.SomeDefaultValue);
        }
        return value.Equals(parameter);
    }

Примечание. Исключение NullReferenceException (10 октября 2018 г.):

Updated the example to remove the possibility of throwing a NullReferenceException. IsChecked is a nullable type so returning Nullable<Boolean> seems a reasonable solution.

Я согласен, я считаю, что это лучшее решение. Кроме того, использование этого преобразования приведет к прерыванию проекта во время компиляции, а не во время выполнения, если значения перечисления изменяются, что является большим преимуществом.

CrimsonX 21.10.2010 03:01

Это, безусловно, гораздо лучшее решение, чем принятое. +1

OrPaz 03.07.2011 17:38

Хорошее решение. Я бы добавил, что это действительно просто конвертер для сравнения двух значений. Он может иметь более общее имя, чем EnumToBooleanConverter, например ComparisonConverter.

MikeKulls 14.07.2011 10:53

@MikeKulls - спасибо! ComparisonConverter теперь часть моего арсенала конвертера :)

Peter Lillevold 05.10.2011 16:10

@ Скотт, очень мило. Этот конвертер хорош в любых случаях, с атрибутом Flags или без него. Но в большинстве случаев было бы глупо использовать этот фильтр непосредственно как преобразователь с перечислением в качестве флагов. Причина в том, что вы должны получить логическое вычисление (| = или ^ =) с предыдущим значением, чтобы получить правильный результат, но конвертер не имеет доступа к предыдущему значению. Затем вы должны добавить логическое значение для каждого значения перечисления и самостоятельно выполнить правильное логическое вычисление в своей модели MVVM. Но спасибо за каждую информацию, очень полезную.

Eric Ouellet 01.05.2012 00:57

@ Эрик Уэлле, спасибо. Кто-то еще обновил мой ответ кодом для Enum Flags. Я сам никогда не использовал это и думал о том, чтобы откатить ответ, так как это может еще больше запутать людей и загромождать ответ.

Scott 01.05.2012 21:35

Собственно, это правильное решение, а не указанное выше. Привязка принятого ответа работает только в одну сторону, но это работает в обе стороны.

l46kok 16.10.2012 04:27

Метод Enum with flags ConvertBack не работает. В своем ответе я пробовал другой метод.

KenGey 11.06.2013 14:03

Вау, это хорошо, к сожалению, в Windows phone нет x: Static, поэтому мне пришлось придерживаться решения Lars.

Fabio Marcolini 18.06.2013 23:57

В Windows Phone 8 (возможно, в случае Win Store Apps) у нас нет x: static, поэтому мы не можем напрямую использовать решение здесь. Однако IDE / Complier достаточно умен и ищет строку по всем строковым литералам (по крайней мере, я предполагаю). например это работает <RadioButton IsChecked = "{Binding TrackingMode, ConverterParameter = Driving, Converter = {StaticResource EnumToBooleanConverter}, Mode = TwoWay}" /> Любые опечатки в Driving будут обнаружены во время разработки / компиляции, а не во время выполнения.

Adarsha 08.11.2014 04:54

В Silverlight нет Binding.DoNothing. Это только WPF. Вместо этого используйте null.

Alexander Vasilyev 13.01.2015 16:01

И в Silverlight также нет x: Static.

Alexander Vasilyev 13.01.2015 16:09

@Scott Верно, он помечен как WPF, и я не проголосовал против, а вместо этого проголосовал за. В то же время, я должен сказать, что я погуглил 'silverlight bind radiobutton enum' и нашел эту страницу :) Google должен настроить свою поисковую систему, чтобы учитывать теги stackoverflow :) Так что лучше оставлять свои комментарии для тех, кто заходит сюда как я.

Alexander Vasilyev 14.01.2015 18:12

Отличный пост! Потратил полдня, пробуя другие (более сложные) решения, но это просто и элегантно. Еще раз спасибо.

Janiek Buysrogge 12.08.2015 15:55

Это решение отлично работает, но исходная радиокнопка не выбирается, когда перечисление уже установлено. Например, у меня есть пациент с перечислением Gender (мужчина и женщина). Когда я запускаю приложение, флажки не устанавливаются. Но когда я проверяю один из них, свойство Gender обновляется правильно.

RvdK 29.10.2015 18:32

@RvdK неважно, это не сработало, потому что я установил имя группы в обоих радиокнопках. Когда удалил, работает.

RvdK 29.10.2015 18:48

@Scott value (найденный в методах Convert() и ConvertBack()) может иметь значение null. Предлагаю написать соответственно return (value?.Equals(parameter) ?? false); и return (((bool?)value ?? false) ? parameter : Binding.DoNothing);. По-прежнему однострочные, по-прежнему чистые и простые для чтения. :)

Yoda 11.01.2017 19:46

Никто не заметил в журнале отладки что-то вроде: [0:] Binding: 'System.Object' can not be converted to type local:YourClass+YourNestedEnumType при снятии галочки?

Cfun 13.08.2020 18:30

Для ответа EnumToBooleanConverter: Вместо возврата DependencyProperty.UnsetValue рассмотрите возможность возврата Binding.DoNothing в случае, когда значение IsChecked переключателя становится ложным. Первый указывает на проблему (и может показать пользователю красный прямоугольник или аналогичные индикаторы проверки), а второй просто указывает, что ничего делать не следует, что и требуется в этом случае.

http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.convertback.aspxhttp://msdn.microsoft.com/en-us/library/system.windows.data.binding.donothing.aspx

В Silverlight нет привязки. Это только WPF. Вместо этого используйте null.

Alexander Vasilyev 13.01.2015 16:00

Связывание. Ничего не пропало из UWP.

BlackICE 27.09.2016 18:00

На основе EnumToBooleanConverter от Скотта. Я заметил, что метод ConvertBack не работает с кодом Enum с флагами.

Я пробовал следующий код:

public class EnumHasFlagToBooleanConverter : IValueConverter
    {
        private object _obj;
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            _obj = value;
            return ((Enum)value).HasFlag((Enum)parameter);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value.Equals(true))
            {
                if (((Enum)_obj).HasFlag((Enum)parameter))
                {
                    // Do nothing
                    return Binding.DoNothing;
                }
                else
                {
                    int i = (int)_obj;
                    int ii = (int)parameter;
                    int newInt = i+ii;
                    return (NavigationProjectDates)newInt;
                }
            }
            else
            {
                if (((Enum)_obj).HasFlag((Enum)parameter))
                {
                    int i = (int)_obj;
                    int ii = (int)parameter;
                    int newInt = i-ii;
                    return (NavigationProjectDates)newInt;

                }
                else
                {
                    // do nothing
                    return Binding.DoNothing;
                }
            }
        }
    }

Единственное, что я не могу заставить работать, - это выполнить приведение из int в targetType, поэтому я жестко запрограммировал его на NavigationProjectDates, перечисление, которое я использую. И, targetType == NavigationProjectDates ...


Изменить для более общего конвертера Flags Enum:

    public class FlagsEnumToBooleanConverter : IValueConverter {
        private int _flags=0;
        public object Convert(object value, Type targetType, object parameter, string language) {
            if (value == null) return false;
            _flags = (int) value;
            Type t = value.GetType();
            object o = Enum.ToObject(t, parameter);
            return ((Enum)value).HasFlag((Enum)o);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            if (value?.Equals(true) ?? false) {
                _flags = _flags | (int) parameter;
            }
            else {
                _flags = _flags & ~(int) parameter;
            }
            return _flags;
        }
    }

Кто-то отредактировал мой ответ, чтобы добавить в код с флагами, поэтому, честно говоря, я никогда не пробовал / не использовал его сам и думал об удалении, поскольку я думаю, что это имеет больше смысла как собственный ответ. Если я смогу найти время позже, я могу попытаться собрать что-нибудь, чтобы протестировать этот код, а также то, что у вас есть, и, возможно, помочь найти лучшее решение вашей проблемы.

Scott 11.06.2013 18:04

Для UWP это не так просто: вы должны перепрыгнуть через лишнюю петлю, чтобы передать значение поля в качестве параметра.

Пример 1

Действительно как для WPF, так и для UWP.

<MyControl>
    <MyControl.MyProperty>
        <Binding Converter = "{StaticResource EnumToBooleanConverter}" Path = "AnotherProperty">
            <Binding.ConverterParameter>
                <MyLibrary:MyEnum>Field</MyLibrary:MyEnum>
            </Binding.ConverterParameter>
        </MyControl>
    </MyControl.MyProperty>
</MyControl>

Пример 2

Действительно как для WPF, так и для UWP.

...
<MyLibrary:MyEnum x:Key = "MyEnumField">Field</MyLibrary:MyEnum>
...

<MyControl MyProperty = "{Binding AnotherProperty, Converter = {StaticResource EnumToBooleanConverter}, ConverterParameter = {StaticResource MyEnumField}}"/>

Пример 3

Действительно только для WPF!

<MyControl MyProperty = "{Binding AnotherProperty, Converter = {StaticResource EnumToBooleanConverter}, ConverterParameter = {x:Static MyLibrary:MyEnum.Field}}"/>

UWP не поддерживает x:Static, поэтому о Пример 3 не может быть и речи; предполагая, что вы используете Пример 1, результатом будет более подробный код. Пример 2 немного лучше, но все же не идеально.

Решение

public abstract class EnumToBooleanConverter<TEnum> : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;

        if (Parameter == null)
            return DependencyProperty.UnsetValue;

        if (Enum.IsDefined(typeof(TEnum), value) == false)
            return DependencyProperty.UnsetValue;

        return Enum.Parse(typeof(TEnum), Parameter).Equals(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;
        return Parameter == null ? DependencyProperty.UnsetValue : Enum.Parse(typeof(TEnum), Parameter);
    }
}

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

public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
    //Nothing to do!
}

Причина, по которой он должен быть помещен в коробку, заключается в том, что, по-видимому, нет возможности ссылаться на тип в методе ConvertBack; бокс позаботится об этом. Если вы воспользуетесь одним из первых двух примеров, вы можете просто сослаться на тип параметра, устраняя необходимость наследования от упакованного класса; если вы хотите сделать все в одну строку и с наименьшей возможной многословностью, последнее решение является идеальным.

Использование похоже на Пример 2, но на самом деле менее подробное.

<MyControl MyProperty = "{Binding AnotherProperty, Converter = {StaticResource MyEnumToBooleanConverter}, ConverterParameter=Field}"/>

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

Это работает и для Флажок.

public class EnumToBoolConverter:IValueConverter
{
    private int val;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int intParam = (int)parameter;
        val = (int)value;

        return ((intParam & val) != 0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        val ^= (int)parameter;
        return Enum.Parse(targetType, val.ToString());
    }
}

Привязка одного перечисления к нескольким флажкам.

Я говорю вам большое "СПАСИБО" за оказанную мне услугу. Для меня это сработало как оберег.

Elham Azadfar 31.12.2017 12:16

Я создал новый класс для обработки привязки RadioButtons и CheckBox к перечислениям. Он работает для помеченных перечислений (с множественным выбором флажков) и неотмеченных перечислений для флажков с одним выбором или переключателей. Он также вообще не требует никаких преобразователей ValueConverters.

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

public class EnumSelection<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
  private T value; // stored value of the Enum
  private bool isFlagged; // Enum uses flags?
  private bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can)
  private T blankValue; // what is considered the "blank" value if it can be deselected?

  public EnumSelection(T value) : this(value, false, default(T)) { }
  public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default(T)) { }
  public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { }
  public EnumSelection(T value, bool canDeselect, T blankValue)
  {
    if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums...
    isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false);

    this.value = value;
    this.canDeselect = canDeselect;
    this.blankValue = blankValue;
  }

  public T Value
  {
    get { return value; }
    set 
    {
      if (this.value.Equals(value)) return;
      this.value = value;
      OnPropertyChanged();
      OnPropertyChanged("Item[]"); // Notify that the indexer property has changed
    }
  }

  [IndexerName("Item")]
  public bool this[T key]
  {
    get
    {
      int iKey = (int)(object)key;
      return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key);
    }
    set
    {
      if (isFlagged)
      {
        int iValue = (int)(object)this.value;
        int iKey = (int)(object)key;

        if (((iValue & iKey) == iKey) == value) return;

        if (value)
          Value = (T)(object)(iValue | iKey);
        else
          Value = (T)(object)(iValue & ~iKey);
      }
      else
      {
        if (this.value.Equals(key) == value) return;
        if (!value && !canDeselect) return;

        Value = value ? key : blankValue;
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void OnPropertyChanged([CallerMemberName] string propertyName = "")
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

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

public enum StartTask
{
  Manual,
  Automatic
}

[Flags()]
public enum DayOfWeek
{
  Sunday = 1 << 0,
  Monday = 1 << 1,
  Tuesday = 1 << 2,
  Wednesday = 1 << 3,
  Thursday = 1 << 4,
  Friday = 1 << 5,
  Saturday = 1 << 6
}

public enum AdditionalOptions
{
  None = 0,
  OptionA,
  OptionB
}

Вот как легко использовать этот класс:

public class MyViewModel : ViewModelBase
{
  public MyViewModel()
  {
    StartUp = new EnumSelection<StartTask>(StartTask.Manual);
    Days = new EnumSelection<DayOfWeek>(default(DayOfWeek));
    Options = new EnumSelection<AdditionalOptions>(AdditionalOptions.None, true, AdditionalOptions.None);
  }

  public EnumSelection<StartTask> StartUp { get; private set; }
  public EnumSelection<DayOfWeek> Days { get; private set; }
  public EnumSelection<AdditionalOptions> Options { get; private set; }
}

И вот как легко связать флажки и переключатели с этим классом:

<StackPanel Orientation = "Vertical">
  <StackPanel Orientation = "Horizontal">
    <!-- Using RadioButtons for exactly 1 selection behavior -->
    <RadioButton IsChecked = "{Binding StartUp[Manual]}">Manual</RadioButton>
    <RadioButton IsChecked = "{Binding StartUp[Automatic]}">Automatic</RadioButton>
  </StackPanel>
  <StackPanel Orientation = "Horizontal">
    <!-- Using CheckBoxes for 0 or Many selection behavior -->
    <CheckBox IsChecked = "{Binding Days[Sunday]}">Sunday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Monday]}">Monday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Tuesday]}">Tuesday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Wednesday]}">Wednesday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Thursday]}">Thursday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Friday]}">Friday</CheckBox>
    <CheckBox IsChecked = "{Binding Days[Saturday]}">Saturday</CheckBox>
  </StackPanel>
  <StackPanel Orientation = "Horizontal">
    <!-- Using CheckBoxes for 0 or 1 selection behavior -->
    <CheckBox IsChecked = "{Binding Options[OptionA]}">Option A</CheckBox>
    <CheckBox IsChecked = "{Binding Options[OptionB]}">Option B</CheckBox>
  </StackPanel>
</StackPanel>
  1. Когда пользовательский интерфейс загружается, будет выбран переключатель «Вручную», и вы можете изменить свой выбор между «Вручную» или «Автоматически», но всегда должен быть выбран любой из них.
  2. Каждый день недели не будет отмечен, но любое их количество может быть отмечено или снято.
  3. «Вариант А» и «Вариант Б» изначально не будут отмечены. Вы можете проверить одно или другое, проверка одного снимет отметку с другого (аналогично RadioButtons), но теперь вы также можете снять отметку с обоих (чего нельзя сделать с WPF RadioButton, поэтому здесь используется CheckBox)

Предположим, у вас есть 3 элемента в перечислении StartTask, например {Undefined, Manual, Automatic}. Вы хотите установить по умолчанию Undefined, потому что, пока пользователь не установит значение, оно не определено. Также: как поступают с SelectedItem? В вашей ViewModel нет SelectedStartTask.

user1040323 06.02.2019 16:14

В моей модели ViewModel свойство StartUp - это объект EnumSelection<StartTask>. Если вы посмотрите определение EnumSelection<T>, вы увидите, что у него есть свойство Value. Таким образом, модель представления не обязательно должна иметь «SelectedStartTask». Вы бы использовали StartUp.Value. А что касается значения по умолчанию Undefined, см. 3-е перечисление AdditionalOptions, у него есть None вместо Undefined, но вы можете изменить его имя на любое другое.

Nick 19.02.2019 21:54

Вы можете создавать переключатели динамически, ListBox может помочь вам сделать это без конвертеров, довольно просто.

Конкретные шаги приведены ниже:

  • создайте ListBox и установите ItemsSource для списка как перечисление MyLovelyEnum и привяжите SelectedItem ListBox к свойству VeryLovelyEnum.
  • тогда будут созданы радиокнопки для каждого ListBoxItem.
  • Шаг 1: добавьте перечисление к статическим ресурсам для вашего Window, UserControl или Grid и т. д.
    <Window.Resources>
        <ObjectDataProvider MethodName = "GetValues"
                            ObjectType = "{x:Type system:Enum}"
                            x:Key = "MyLovelyEnum">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName = "local:MyLovelyEnum" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
  • Шаг 2: используйте поле списка и Control Template, чтобы заполнить каждый элемент внутри как переключатель
    <ListBox ItemsSource = "{Binding Source = {StaticResource MyLovelyEnum}}" SelectedItem = "{Binding VeryLovelyEnum, Mode=TwoWay}" >
        <ListBox.Resources>
            <Style TargetType = "{x:Type ListBoxItem}">
                <Setter Property = "Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <RadioButton
                                Content = "{TemplateBinding ContentPresenter.Content}"
                                IsChecked = "{Binding Path=IsSelected,
                                RelativeSource = {RelativeSource TemplatedParent},
                                Mode=TwoWay}" />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.Resources>
    </ListBox>

Преимущество: если когда-нибудь ваш класс перечисления изменится, вам не нужно обновлять графический интерфейс (файл XAML).

Рекомендации:https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

Это не сработает, если EnumProperty уже имеет значение. public MyEnum EProperty {get; set;} = MyEnum.Value1. RadioButton не проверяется

Daniel DirtyNative Martin 14.12.2020 15:22

Решение TwoWay Binding для UWP, которое использует Nullable:

Часть C#:

public class EnumConverter : IValueConverter
{
    public Type EnumType { get; set; }
    public object Convert(object value, Type targetType, object parameter, string lang)
    {
        if (parameter is string enumString)
        {
            if (!Enum.IsDefined(EnumType, value)) throw new ArgumentException("value must be an Enum!");
            var enumValue = Enum.Parse(EnumType, enumString);
            return enumValue.Equals(value);
        }
        return value.Equals(Enum.ToObject(EnumType,parameter));
    }

    public object ConvertBack(object value, Type targetType, object parameter, string lang)
    {
        if (parameter is string enumString)
            return value?.Equals(true) == true ? Enum.Parse(EnumType, enumString) : null;
        return value?.Equals(true) == true ? Enum.ToObject(EnumType, parameter) : null;
    }
}

Здесь значение null действует как Binding.DoNothing.

private YourEnum? _yourEnum = YourEnum.YourDefaultValue; //put a default value here
public YourEnum? YourProperty
{
    get => _yourEnum;
    set{
        if (value == null) return;
        _yourEnum = value;
    }
}

Часть Xaml:

...
<Page.Resources>
    <ResourceDictionary>
        <helper:EnumConverter x:Key = "YourConverter" EnumType = "yournamespace:YourEnum" />
    </ResourceDictionary>
</Page.Resources>
...
<RadioButton GroupName = "YourGroupName" IsChecked = "{Binding Converter = {StaticResource YourConverter}, Mode=TwoWay, Path=YourProperty, ConverterParameter=YourEnumString}">
    First way (parameter of type string)
</RadioButton>
<RadioButton GroupName = "LineWidth">
    <RadioButton.IsChecked>
        <Binding
            Converter = "{StaticResource PenWidthConverter}"
            Mode = "TwoWay"   Path = "PenWidth">
            <Binding.ConverterParameter>
                <yournamespace:YourEnum>YourEnumString</yournamespace:YourEnum>
            </Binding.ConverterParameter>
        </Binding>
    </RadioButton.IsChecked>
    Second way (parameter of type YourEnum (actually it was converted to int when passed to converter))
</RadioButton>

Другие вопросы по теме