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>