如何将单选按钮绑定到枚举?

时间:2008-12-29 11:35:01

标签: wpf data-binding enums radio-button

我有一个这样的枚举:

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绑定到属性以进行正确的双向绑定?

10 个答案:

答案 0 :(得分:533)

您可以进一步简化已接受的答案。您可以显式传入枚举值而不是字符串表示形式,而不是在xaml中将枚举输入为xaml中的字符串并在转换器中执行更多工作,而在CrimsonX注释时,错误会在编译时而不是运行时抛出:< / p>

ConverterParameter = {x:Static local: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(18年10月10日):

更新了示例以消除抛出NullReferenceException的可能性。 IsChecked是可以为空的类型,因此返回Nullable<Boolean>似乎是一个合理的解决方案。

注意 - 同一个容器中的多组RadioButton(2011年2月17日):

在xaml中,如果单选按钮共享同一个父容器,则选择一个将取消选择该容器中的所有其他容器(即使它们绑定到不同的属性)。因此,请尝试将绑定到公共属性的RadioButton保存在自己的容器中,就像堆栈面板一样。如果您的相关RadioButton无法共享单个父容器,请将每个RadioButton的GroupName属性设置为一个公共值,以便对它们进行逻辑分组。

注 - 嵌套在类中的枚举类型(2011年4月28日):

如果您的枚举类型嵌套在类中(而不是直接嵌套在命名空间中),您可以使用'+'语法来访问XAML中的枚举,如问题{{3中的(未标记)答案中所述}}:

ConverterParameter = {x:静态本地: YourClass + YourNestedEnumType.Enum1}

然而,由于这个Unable to find enum type for static reference in WPF,VS2010中的设计器将不再加载声明"Type 'local:YourClass+YourNestedEnumType' was not found.",但该项目确实编译并成功运行。当然,如果能够直接将枚举类型移动到命名空间,则可以避免此问题。

编辑(2010年12月16日):

感谢anon建议返回Binding.DoNothing而不是DependencyProperty.UnsetValue。

编辑(2011年4月5日):

简化了ConvertBack的if-else以使用三元运算符。

编辑(12月27日):

如果使用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;
    }
}

编辑(2015年5月7日):

在Nullable Enum的情况下(在问题中>,但在某些情况下可能需要,例如ORM从DB返回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);
    }

答案 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>
  1. 当加载UI时,将选择“手动”单选按钮,您可以在“手动”或“自动”之间更改选择,但必须始终选择其中一个。
  2. 一周中的每一天都将取消选中,但可以选中或取消选中任意数量。
  3. “选项A”和“选项B”最初都将取消选中。你可以检查一个或另一个,检查一个将取消选中另一个(类似于RadioButtons),但现在你也可以取消选中它们(你不能用WPF的RadioButton,这就是这里使用CheckBox的原因)

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

我唯一无法工作的是从inttargetType进行投射,因此我将其硬编码为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可以帮助您完成此操作,而无需使用转换器,非常简单。

具体步骤如下:

  • 创建一个ListBox并将列表框的ItemsSource设置为枚举MyLovelyEnum,并将ListBox的SelectedItem绑定到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>

优点是:如果有一天您的枚举类更改,则无需更新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>