首页 文章

如何将RadioButtons绑定到枚举?

提问于
浏览
373

我有这样的枚举:

public enum MyLovelyEnum
{
  FirstSelection,
  TheOtherSelection,
  YetAnotherOne
};

我在DataContext中获得了一个属性:

public MyLovelyEnum VeryLovelyEnum { get; set; }

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

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

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

9 回答

  • 520

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

    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>
    
  • 3

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

    ConverterParameter = {x:静态本地: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中,如果单选按钮共享相同的父容器,则选择一个将取消选择绑定到在其自己的容器中组合在一起的公共属性的所有其他's within that container (even if they are bound to a different property). So try to keep your RadioButton',如堆栈面板 . 如果您的相关RadioButton不能共享单个父容器,则将每个RadioButton的GroupName属性设置为公共值以对它们进行逻辑分组 .

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

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

    ConverterParameter = {x:静态local:YourClass YourNestedEnumType.Enum1}

    但是,由于这个Microsoft Connect Issue,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(在问题中是 not ,但在某些情况下可能需要,例如ORM从DB返回null,或者只要在程序逻辑中没有提供值就有意义),请记住添加转换方法中的初始null检查并返回相应的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);
        }
    
  • 1

    对于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

  • 25

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

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

  • 5

    对于UWP,它不是那么简单:你必须通过一个额外的环来传递一个字段值作为参数 .

    Example 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>
    

    Example 2

    适用于WPF和UWP .

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

    Example 3

    仅对WPF有效!

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

    UWP不支持 x:Static 所以示例3是不可能的;假设您使用示例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);
        }
    }
    

    然后,对于您希望支持的每种类型,定义一个包含枚举类型的转换器 .

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

    它必须装箱的原因是因为似乎无法在 ConvertBack 方法中引用该类型;拳击照顾那个 . 如果您使用前两个示例中的任何一个,您只需引用参数类型,从而无需继承盒装类;如果你希望在一行中完成所有这一切并且可能的冗长程度最低,那么后一种解决方案是理想的 .

    用法类似于例2,但实际上并不那么冗长 .

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

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

  • 0

    通过将单选按钮绑定到任何类型(枚举,布尔,字符串,整数等)的能力扩展了上面的好主意,并在此提供了工作示例代码:

    http://www.codeproject.com/Tips/720497/Binding-Radio-Buttons-to-a-Single-Property

  • 350

    我已经创建了一个新类来处理绑定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>
    
    • 当UI加载时,将选择"Manual"单选按钮,您可以在"Manual"或"Automatic"之间更改选择,但必须始终选择其中一个 .

    • 将取消选中一周中的每一天,但可以选中或取消选中任意数量的星期几 .

    • "Option A"和"Option B"最初都将取消选中 . 你可以检查一个或另一个,检查一个将取消选中另一个(类似于RadioButtons),但现在你也可以取消选中它们(你不能用WPF的RadioButton,这就是为什么在这里使用CheckBox)

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

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

  • 1

    基于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 ......


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

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

相关问题