我有一个这样的枚举:
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
我的DataContext中有一个属性:
public MyLovelyEnum VeryLovelyEnum { get; set; }
我的WPF客户端中有三个RadioButtons。
<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>
现在如何将RadioButtons绑定到属性以进行正确的双向绑定?
答案 0 :(得分:533)
您可以进一步简化已接受的答案。您可以显式传入枚举值而不是字符串表示形式,而不是在xaml中将枚举输入为xaml中的字符串并在转换器中执行更多工作,而在CrimsonX注释时,错误会在编译时而不是运行时抛出:< / p>
<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;
}
}
IsChecked
是可以为空的类型,因此返回Nullable<Boolean>
似乎是一个合理的解决方案。
ConverterParameter = {x:静态本地: YourClass + YourNestedEnumType.Enum1}
然而,由于这个Unable to find enum type for static reference in WPF,VS2010中的设计器将不再加载声明"Type 'local:YourClass+YourNestedEnumType' was not found."
,但该项目确实编译并成功运行。当然,如果能够直接将枚举类型移动到命名空间,则可以避免此问题。
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;
}
}
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);
}
答案 1 :(得分:366)
您可以使用更通用的转换器
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-Part中你使用:
<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>
答案 2 :(得分:25)
对于EnumToBooleanConverter答案: 而不是返回DependencyProperty.UnsetValue而是考虑在单选按钮IsChecked值变为false的情况下返回Binding.DoNothing。 前者表示存在问题(并且可能向用户显示红色矩形或类似的验证指示符),而后者仅表示不应该执行任何操作,这就是在这种情况下所需的。
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
答案 3 :(得分:5)
我会在ListBox中使用Radio Buttons,然后绑定到SelectedValue。
这是关于此主题的旧帖子,但基本想法应该相同:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/
答案 4 :(得分:3)
对于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}"/>
缺点是您必须为您希望支持的每种类型定义转换器。
答案 5 :(得分:1)
这也适用于 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());
}
}
将单个枚举绑定到多个复选框。
答案 6 :(得分:1)
我创建了一个新类来处理绑定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>
答案 7 :(得分:0)
基于Scott的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; } }
答案 8 :(得分:0)
您可以动态创建单选按钮,ListBox
可以帮助您完成此操作,而无需使用转换器,非常简单。
具体步骤如下:
MyLovelyEnum
,并将ListBox的SelectedItem绑定到VeryLovelyEnum
属性。 <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>
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>
优点是:如果有一天您的枚举类更改,则无需更新GUI(XAML文件)。
参考: https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/
答案 9 :(得分:0)
使用 Nullable 的 UWP 双向绑定解决方案:
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;
}
}
XML 部分:
...
<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>