Wpf 如何将单选按钮绑定到枚举?
我有这样一个枚举:Wpf 如何将单选按钮绑定到枚举?,wpf,data-binding,enums,radio-button,Wpf,Data Binding,Enums,Radio Button,我有这样一个枚举: public enum MyLovelyEnum { FirstSelection, TheOtherSelection, YetAnotherOne }; 我的DataContext中有一个属性: public MyLovelyEnum VeryLovelyEnum { get; set; } 我的WPF客户端有三个单选按钮 <RadioButton Margin="3">First Selection</RadioButton
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
我的DataContext中有一个属性:
public MyLovelyEnum VeryLovelyEnum { get; set; }
我的WPF客户端有三个单选按钮
<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>
第一选择
另一种选择
还有一个
现在,如何将RadioButtons绑定到属性以进行适当的双向绑定?我将使用列表框中的RadioButtons,然后绑定到SelectedValue
这是一个关于这个主题的老话题,但基本思想应该是相同的:您可以使用更通用的转换器
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}
编辑(2010年12月16日): 感谢anon建议返回Binding.DoNothing而不是dependencProperty.unset值
注-同一容器中有多组单选按钮(2011年2月17日): 在xaml中,如果单选按钮共享同一父容器,则选择其中一个将取消选择该容器中的所有其他按钮(即使它们绑定到不同的属性)。因此,尝试将绑定到公共属性的RadioButton分组到它们自己的容器中,就像堆栈面板一样。如果相关的RadioButton无法共享单个父容器,则将每个RadioButton的GroupName属性设置为公共值以对其进行逻辑分组
编辑(2011年4月5日): 简化ConvertBack的if-else使用三元运算符
注意-嵌套在类中的枚举类型(2011年4月28日): 如果枚举类型嵌套在类中(而不是直接嵌套在命名空间中),则可以使用“+”语法访问XAML中的枚举,如问题的(未标记)答案中所述: ConverterParameter={x:Static local:YourClass+YourNestedEnumType.Enum1} 但是,由于这个原因,VS2010中的设计器将不再加载声明
“找不到Type'local:YourClass+yournestedumType”。
,但项目确实编译并成功运行。当然,如果能够将枚举类型直接移动到名称空间,则可以避免此问题
编辑(2012年1月27日): 如果使用枚举标志,转换器将如下所示:
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;
}
}
编辑(2015年5月7日): 对于可为空的枚举(即问题中未提出的但在某些情况下可能需要,例如ORM从DB返回null或在程序逻辑中可能没有提供值时),请记住在Convert方法中添加初始null检查并返回适当的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);
}
注-NullReferenceException(2018年10月10日):
更新了示例,以消除引发NullReferenceException的可能性
IsChecked
是一种可空类型,因此返回nullable
似乎是一个合理的解决方案。对于EnumToBooleanConverter答案:
而不是返回依赖性属性。unStValk考虑返回绑定。DooOnter用于单选按钮被选中的值为false。
前者表示存在问题(可能会向用户显示一个红色矩形或类似的验证指示器),而后者只表示不应执行任何操作,这正是在这种情况下所需要的
基于Scott提供的EnumToBooleanConverter。 我注意到ConvertBack方法在带有标志代码的枚举上不起作用 我尝试了以下代码:
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
编辑更多通用标志枚举转换器:
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;
}
}
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;
}
}
公共类FlagsEnumToBooleanConverter:IValueConverter{
私有int_标志=0;
公共对象转换(对象值、类型targetType、对象参数、字符串语言){
if(value==null)返回false;
_标志=(int)值;
类型t=value.GetType();
对象o=Enum.ToObject(t,参数);
返回((枚举)值).HasFlag((枚举)o);
}
公共对象转换回(对象值、类型targetType、对象参数、字符串语言)
{
如果(值?等于(真)?假){
_flags=_flags |(int)参数;
}
否则{
_flags=_flags&~(int)参数;
}
返回(u)标志;;
}
}
对于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>
...
<MyLibrary:MyEnum x:Key="MyEnumField">Field</MyLibrary:MyEnum>
...
<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={StaticResource MyEnumField}}"/>
然后,对于您希望支持的每种类型,定义一个用于装箱枚举类型的转换器
public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
//Nothing to do!
}
缺点是您必须为希望支持的每种类型定义一个转换器 这也适用于复选框
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());
}
}
将单个枚举绑定到多个复选框。我创建了一个新类来处理将单选按钮和复选框绑定到枚举的操作。它适用于标记的枚举(具有多个复选框选择)和非标记的枚举(用于单选复选框或单选按钮)。它也不需要任何值转换器 这在一开始看起来可能更复杂,但是,一旦您将这个类复制到您的项目中,它就完成了。它是通用的,因此可以轻松地对任何枚举进行重用
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));
}
}
公共类EnumSelection:InotifyProperty已更改,其中T:struct、IComparable、IFormattable、IConvertible
{
私有T值;//的存储值
public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
//Nothing to do!
}
<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());
}
}
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>