Как привязать RadioButtons к перечислению? - PullRequest
386 голосов
/ 29 декабря 2008

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

public enum MyLovelyEnum
{
  FirstSelection,
  TheOtherSelection,
  YetAnotherOne
};

Я получил свойство в моем DataContext:

public MyLovelyEnum VeryLovelyEnum { get; set; }

И я получил три RadioButton в моем клиенте WPF.

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

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

Ответы [ 8 ]

535 голосов
/ 26 мая 2010

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

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

Примечание - исключение NullReferenceException (10 октября '18):

Обновлен пример, чтобы исключить возможность создания исключения NullReferenceException. IsChecked является обнуляемым типом, поэтому возврат Nullable<Boolean> кажется разумным решением.

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

В xaml, если переключатели совместно используют один и тот же родительский контейнер, выбор одного из них отменяет выбор всех других в этом контейнере (даже если они связаны с другим свойством). Поэтому постарайтесь сохранить ваши RadioButton, связанные с общим свойством, сгруппированными в их собственном контейнере, как панель стека. В тех случаях, когда связанные с вами RadioButtons не могут совместно использовать один родительский контейнер, установите для свойства GroupName каждого RadioButton общее значение для их логической группировки.

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

Если ваш тип перечисления вложен в класс (а не непосредственно в пространство имен), вы можете использовать синтаксис «+» для доступа к перечислению в XAML, как указано в (не отмеченном) ответе на вопрос Невозможно найти тип перечисления для статической ссылки в WPF :

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

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

Редактировать (16 декабря '10):

Спасибо anon за предложение вернуть Binding.DoNothing вместо DependencyProperty.UnsetValue.

Редактировать (5 апреля '11):

Упрощенное использование ConvertBack if-else для использования тернарного оператора.

Редактировать (27 января '12):

При использовании флагов Enum конвертер будет выглядеть следующим образом:
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 мая '15):

В случае Nullable Enum (то есть не , заданного в вопросе, но может быть необходимо в некоторых случаях, например, ORM возвращает ноль из БД или всякий раз, когда в логике программы может иметь смысл значение не при условии), не забудьте добавить начальную нулевую проверку в методе Convert и вернуть соответствующее значение bool, которое обычно равно false (если вы не хотите выбирать какую-либо радиокнопку), как показано ниже:
    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);
    }
367 голосов
/ 02 января 2009

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

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>
25 голосов
/ 03 сентября 2010

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

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

5 голосов
/ 29 декабря 2008

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

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

3 голосов
/ 18 марта 2017

Для 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);
    }
}

Затем для каждого типа, который вы хотите поддерживать, определите конвертер, который включает тип enum.

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

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

Использование напоминает Пример 2 , но на самом деле оно менее многословно.

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

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

1 голос
/ 02 апреля 2018

Я создал новый класс для обработки привязки 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. «Вариант A» и «Вариант B» будут изначально не отмечены. Вы можете проверить одно или другое, отметив, что один снимает флажок с другого (аналогично RadioButton), но теперь вы также можете снять оба флажка (чего нельзя сделать с RadioButton в WPF, поэтому здесь используется CheckBox)
1 голос
/ 25 декабря 2017

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

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

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

0 голосов
/ 11 июня 2013

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

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

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