wpf 如何将 RadioButtons 绑定到枚举?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/397556/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:55:20  来源:igfitidea点击:

How to bind RadioButtons to an enum?

wpfdata-bindingenumsradio-button

提问by Sam

I've got an enum like this:

我有一个这样的枚举:

public enum MyLovelyEnum
{
    FirstSelection,
    TheOtherSelection,
    YetAnotherOne
};

I got a property in my DataContext:

我的 DataContext 中有一个属性:

public MyLovelyEnum VeryLovelyEnum { get; set; }

And I got three RadioButtons in my WPF client.

我的 WPF 客户端中有三个 RadioButton。

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

Now how do I bind the RadioButtons to the property for a proper two-way binding?

现在如何将 RadioButtons 绑定到属性以进行正确的双向绑定?

采纳答案by Lars

You could use a more generic converter

您可以使用更通用的转换器

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
}

And in the XAML-Part you use:

在您使用的 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>

回答by Scott

You can further simplify the accepted answer. Instead of typing out the enums as strings in xaml and doing more work in your converter than needed, you can explicitly pass in the enum value instead of a string representation, and as CrimsonX commented, errors get thrown at compile time rather than runtime:

您可以进一步简化已接受的答案。与其在 xaml 中将枚举作为字符串输入并在转换器中做比需要更多的工作,您可以显式传入枚举值而不是字符串表示,并且正如 CrimsonX 所评论的,错误是在编译时而不是运行时抛出的:

ConverterParameter={x:Static local:YourEnumType.Enum1}

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>

Then simplify the converter:

然后简化转换器:

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


Edit (Dec 16 '10):

编辑(2010 年 12 月 16 日):

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



Note - Multiple groups of RadioButtons in same container (Feb 17 '11):

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

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



Edit (Apr 5 '11):

编辑(2011 年 4 月 5 日):

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



Note - Enum type nested in a class (Apr 28 '11):

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

如果您的枚举类型嵌套在一个类中(而不是直接在命名空间中),您可以使用“+”语法来访问 XAML 中的枚举,如问题的(未标记)答案中所述 Unable to find enum type for static reference in WPF无法在 WPF 中找到静态引用的枚举类型

ConverterParameter={x:Static local:YourClass+YourNestedEnumType.Enum1}

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

Due to this Microsoft Connect Issue, however, the designer in VS2010 will no longer load stating "Type 'local:YourClass+YourNestedEnumType' was not found.", but the project does compile and run successfully. Of course, you can avoid this issue if you are able to move your enum type to the namespace directly.

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



Edit (Jan 27 '12):

编辑(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;
    }
}


Edit (May 7 '15):

编辑(2015 年 5 月 7 日):

如果是可空枚举(即 not问题中未提出,但在某些情况下可能需要,例如 ORM 从 DB 返回 null 或在程序逻辑中未提供值可能有意义的任何时候),请记住在 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);
    }


Note - NullReferenceException (Oct 10 '18):

注 - NullReferenceException(2018 年 10 月 10 日):

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

回答by anon

For the EnumToBooleanConverter answer: Instead of returning DependencyProperty.UnsetValue consider returning Binding.DoNothing for the case where the radio button IsChecked value becomes false. The former indicates a problem (and might show the user a red rectangle or similar validation indicators) while the latter just indicates that nothing should be done, which is what is wanted in that case.

对于 EnumToBooleanConverter 答案:在单选按钮 IsChecked 值变为 false 的情况下,考虑返回 Binding.DoNothing 而不是返回 DependencyProperty.UnsetValue。前者表示存在问题(并可能向用户显示红色矩形或类似的验证指示符),而后者仅表示不应执行任何操作,这正是这种情况所需要的。

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

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

回答by Martin Moser

I would use the RadioButtons in a ListBox, and then bind to the SelectedValue.

我会在 ListBox 中使用 RadioButtons,然后绑定到 SelectedValue。

This is an older thread about this topic, but the base idea should be the same: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/

这是关于此主题的较旧线程,但基本思想应该相同:http: //social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/

回答by James M

For UWP, it is not so simple: You must jump through an extra hoop to pass a field value as a parameter.

对于 UWP,它并不是那么简单:您必须跳过一个额外的圈子才能将字段值作为参数传递。

Example 1

示例 1

Valid for both WPF and UWP.

对 WPF 和 UWP 均有效。

<MyControl>
    <MyControl.MyProperty>
        <Binding Converter="{StaticResource EnumToBooleanConverter}" Path="AnotherProperty">
            <Binding.ConverterParameter>
                <MyLibrary:MyEnum>Field</MyLibrary:MyEnum>
            </Binding.ConverterParameter>
        </MyControl>
    </MyControl.MyProperty>
</MyControl>

Example 2

示例 2

Valid for both WPF and UWP.

对 WPF 和 UWP 均有效。

...
<MyLibrary:MyEnum x:Key="MyEnumField">Field</MyLibrary:MyEnum>
...

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={StaticResource MyEnumField}}"/>

Example 3

示例 3

Valid only for WPF!

仅对 WPF 有效!

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static MyLibrary:MyEnum.Field}}"/>

UWP doesn't support x:Staticso Example 3is out of the question; assuming you go with Example 1, the result is more verbose code. Example 2is slightly better, but still not ideal.

UWP 不支持,x:Static所以示例 3是不可能的;假设您使用Example 1,结果是更冗长的代码。示例 2稍微好一点,但仍然不理想。

Solution

解决方案

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

Then, for each type you wish to support, define a converter that boxes the enum type.

然后,对于您希望支持的每种类型,定义一个将枚举类型装箱的转换器。

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

The reason it must be boxed is because there's seemingly no way to reference the type in the ConvertBackmethod; the boxing takes care of that. If you go with either of the first two examples, you can just reference the parameter type, eliminating the need to inherit from a boxed class; if you wish to do it all in one line and with least verbosity possible, the latter solution is ideal.

它必须被装箱的原因是因为在方法中似乎没有办法引用类型ConvertBack;拳击会照顾到这一点。如果使用前两个示例中的任何一个,则可以只引用参数类型,无需从装箱类继承;如果您希望在一行中完成所有操作并尽可能减少冗长,则后一种解决方案是理想的。

Usage resembles Example 2, but is, in fact, less verbose.

用法与示例 2类似,但实际上不那么冗长。

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

The downside is you must define a converter for each type you wish to support.

缺点是您必须为您希望支持的每种类型定义一个转换器。

回答by Ali Bayat

This work for Checkboxtoo.

这也适用于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());
    }
}

Binding a single enum to multiple checkboxes.

将单个枚举绑定到多个复选框。

回答by Nick

I've created a new class to handle binding RadioButtons and CheckBoxes to enums. It works for flagged enums (with multiple checkbox selections) and non-flagged enums for single-selection checkboxes or radio buttons. It also requires no ValueConverters at all.

我创建了一个新类来处理将 RadioButtons 和 CheckBoxes 绑定到枚举。它适用于带标记的枚举(具有多个复选框选择)和未标记的枚举用于单选复选框或单选按钮。它还完全不需要 ValueConverters。

This might look more complicated at first, however, once you copy this class into your project, it's done. It's generic so it can easily be reused for any enum.

起初这可能看起来更复杂,但是,一旦您将此类复制到您的项目中,它就完成了。它是通用的,因此可以很容易地重用于任何枚举。

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

And for how to use it, let's say you have an enum for running a task manually or automatically, and can be scheduled for any days of the week, and some optional options...

至于如何使用它,假设您有一个用于手动或自动运行任务的枚举,并且可以安排在一周中的任何一天,还有一些可选选项......

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
}

Now, here's how easy it is to use this class:

现在,使用这个类是多么容易:

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

And here's how easy it is to bind checkboxes and radio buttons with this class:

这是使用此类绑定复选框和单选按钮是多么容易:

<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. When the UI loads, the "Manual" radio button will be selected and you can alter your selection between "Manual" or "Automatic" but either one of them must always be selected.
  2. Every day of the week will be unchecked, but any number of them can be checked or unchecked.
  3. "Option A" and "Option B" will both initially be unchecked. You can check one or the other, checking one will uncheck the other (similar to RadioButtons), but now you can also uncheck both of them (which you cannot do with WPF's RadioButton, which is why CheckBox is being used here)
  1. 加载 UI 时,将选择“手动”单选按钮,您可以在“手动”或“自动”之间更改选择,但必须始终选择其中之一。
  2. 一周中的每一天都不会被选中,但可以选中或取消选中任意数量的天。
  3. “选项 A”和“选项 B”最初都不会被选中。您可以选中一个或另一个,选中一个将取消选中另一个(类似于 RadioButtons),但现在您也可以取消选中它们两个(WPF 的 RadioButton 不能这样做,这就是这里使用 CheckBox 的原因)

回答by KenGey

Based on the EnumToBooleanConverter from Scott. I noticed that the ConvertBack method doesn't work on the Enum with flags code.

基于 Scott 的 EnumToBooleanConverter。我注意到 ConvertBack 方法在带有标志代码的 Enum 上不起作用。

I've tried the following code:

我试过以下代码:

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

The only thing that I can't get to work is to do a cast from intto targetTypeso I made it hardcoded to NavigationProjectDates, the enum that I use. And, targetType == NavigationProjectDates...

我唯一无法开始工作的是从inttotargetType进行强制转换NavigationProjectDates,因此我将其硬编码为,即我使用的枚举。而且,targetType == NavigationProjectDates...



Edit for more generic Flags Enum converter:

编辑更通用的标志枚举转换器:

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