Как привязать RadioButtons к перечислению?
У меня есть перечисление как это:
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
Я получил свойство в моем DataContext:
public MyLovelyEnum VeryLovelyEnum { get; set; }
И я получил три RadioBut ton в моем клиенте WPF.
<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>
Теперь, как мне привязать RadioButtons к свойству для правильной двусторонней привязки?
12 ответов
Вы можете использовать более общий конвертер
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>
Вы можете еще больше упростить принятый ответ. Вместо того, чтобы печатать перечисления в виде строк в 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;
}
}
Примечание - NullReferenceException (10 октября '18):
Обновлен пример, чтобы исключить возможность создания исключения NullReferenceException.IsChecked
обнуляемый тип, так что возвращение Nullable<Boolean>
кажется разумным решением.Примечание. Несколько групп радиокнопок в одном контейнере (17 февраля 2011 г.):
В xaml, если переключатели совместно используют один и тот же родительский контейнер, выбор одного из них отменяет выбор всех других в этом контейнере (даже если они связаны с другим свойством). Поэтому постарайтесь сохранить ваши RadioButton, связанные с общим свойством, сгруппированными в их собственном контейнере, как панель стека. В тех случаях, когда ваши связанные RadioButtons не могут совместно использовать один родительский контейнер, установите для свойства GroupName каждого RadioButton общее значение, чтобы логически сгруппировать их.Примечание. Тип Enum, вложенный в класс (28 апреля '11):
Если ваш тип перечисления вложен в класс (а не непосредственно в пространство имен), вы можете использовать синтаксис "+" для доступа к перечислению в XAML, как указано в (не помеченном) ответе на вопрос " Невозможно найти" Тип enum для статической ссылки в WPF:ConverterParameter = {x: Статический локальный: YourClass + YourNestedEnumType.Enum1}
Из-за этой проблемы Microsoft Connect, однако, дизайнер в 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, которое, как правило, ложно (если вы не хотите, чтобы какая-либо радиокнопка была выбрана), как показано ниже: 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);
}
Ответ 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
Я бы использовал RadioButtons в ListBox, а затем привязать к SelectedValue.
Это старая ветка на эту тему, но основная идея должна быть такой же: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/
Я создал новый класс для обработки привязки RadioButtons и CheckBoxes к перечислениям. Он работает для помеченных перечислений (с несколькими вариантами флажков) и непомеченных перечислений для флажков с одним выбором или переключателей. Это также не требует никаких 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>
- Когда пользовательский интерфейс загружается, будет выбран переключатель "Ручной", и вы можете изменить свой выбор между "Ручной" или "Автоматический", но всегда должен быть выбран любой из них.
- Каждый день недели будет не отмечен, но любое количество из них может быть проверено или не отмечено.
- "Вариант A" и "Вариант B" будут изначально не отмечены. Вы можете проверить одно или другое, отметив, что одно снимает флажок с другого (аналогично RadioButton), но теперь вы также можете снять оба флажка (чего нельзя сделать с RadioButton WPF, именно поэтому CheckBox используется здесь)
Для 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}"/>
Недостатком является то, что вы должны определить конвертер для каждого типа, который хотите поддерживать.
Один из способов справиться с этим - иметь отдельные свойства bool в вашем
ViewModel
класс. Вот как я справился с такой ситуацией:
ViewModel:
public enum MyLovelyEnum { FirstSelection, TheOtherSelection, YetAnotherOne };
private MyLovelyEnum CurrentSelection;
public bool FirstSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.FirstSelection;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.FirstSelection;
}
}
public bool TheOtherSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.TheOtherSelection;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.TheOtherSelection;
}
}
public bool YetAnotherOneSelectionProperty
{
get
{
return CurrentSelection == MyLovelyEnum.YetAnotherOne;
}
set
{
if (value)
CurrentSelection = MyLovelyEnum.YetAnotherOne;
}
}
XAML:
<RadioButton IsChecked="{Binding SimilaritySort, Mode=TwoWay}">Similarity</RadioButton>
<RadioButton IsChecked="{Binding DateInsertedSort, Mode=TwoWay}">Date Inserted</RadioButton>
<RadioButton IsChecked="{Binding DateOfQuestionSort, Mode=TwoWay}">Date of Question</RadioButton>
<RadioButton IsChecked="{Binding DateModifiedSort, Mode=TwoWay}">Date Modified</RadioButton>
Он не такой надежный или динамичный, как некоторые другие решения, но приятно то, что он очень самодостаточен и не требует создания специальных конвертеров или чего-то подобного.
Вы можете создавать переключатели динамически, ListBox
может помочь вам сделать это без конвертеров, довольно просто.
Ниже приведены конкретные шаги: создайте ListBox и установите ItemsSource для списка в качестве перечисления. MyLovelyEnum
и привязка SelectedItem ListBox к VeryLovelyEnum
свойство. Затем будут созданы радиокнопки для каждого ListBoxItem.
- Шаг 1: добавьте перечисление к статическим ресурсам для вашего окна, 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/
Эта работа для Checkbox тоже.
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());
}
}
Привязка одного перечисления к нескольким флажкам.
Расширены отличные идеи, приведенные выше, с возможностью привязки переключателей к любому типу (перечисление, логическое значение, строка, целое число и т. Д.) И предоставили рабочий пример кода здесь:
http://www.codeproject.com/Tips/720497/Binding-Radio-Buttons-to-a-Single-Property
Решение 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
value действует как 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>
Основано на 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:
открытый класс FlagsEnumToBooleanConverter: IValueConverter { private int _flags=0; открытый объект Convert(значение объекта, тип targetType, параметр объекта, строковый язык) { if (value == null) return false; _flags = (int) значение; Тип t = значение.GetType(); объект o = Enum.ToObject(t, параметр); return ((Enum) значение).HasFlag((Enum)o); } открытый объект ConvertBack(значение объекта, тип targetType, параметр объекта, язык строк) { if (значение?.Equals(true)?? false) { _flags = _flags | (int) параметр; } еще { _flags = _flags & ~(int) параметр; } вернуть _flags; } }