如何将RadioButtons绑定到枚举?


406

我有一个像这样的枚举:

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


3
如果您希望在不指定XAML中指定单个RadioButton的情况下执行此操作,则建议您将ListBox绑定到诸如thisthis的枚举值,并且该项目模板将被覆盖以使用this的 RadioButtons 。
雷切尔(Rachel)2015年

Answers:


389

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

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>

51
对我来说就像一个魅力。另外,我修改了ConvertBack,使其在“ false”时也返回UnsetValue,因为silverlight(并且大概是WPF正确)两次调用该转换器-一次取消设置旧的单选按钮值,再一次设置新的按钮。我在属性设置器上挂了其他东西,所以我只希望它调用一次。-如果(parameterString == null || value.Equals(false))返回DependencyProperty.UnsetValue;
MarcE 2010年

8
据我所知,除非单选按钮位于不同的组中,否则必须这样做(默认情况下,没有设置GroupName且具有相同父级的AFAIK按钮位于同一组中)。否则,设置属性“ bounce”的调用将导致异常行为。
nlawalker 2010年

2
是的,但是如果您在设置为false时在转换器中调用Unset,则它不是真正的EnumToBooleanConverter,而是EnumToRadioButtonConverter。因此,我改为检查属性设置器中的值是否不同:if(_myEnumBackingField == value)return;
斯特凡

8
此解决方案上的绑定只能单向正确运行。我无法通过将绑定属性分配给其他值来以编程方式切换单选按钮。如果您想要一个适当的工作且更好的解决方案,请使用scott的方法。
l46kok 2012年

2
@Marc,在这种情况下返回“ Binding.DoNothing”而不是“ DependencyProperty.UnsetValue”不是正确的事情吗?
Mark A. Donohoe 2013年

559

您可以进一步简化接受的答案。您可以显式传递枚举值而不是字符串表示形式,而不必在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;
    }
}

编辑(2010年12月16日):

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


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

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


编辑(11年5月5日):

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


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

如果您的枚举类型嵌套在一个类(而不是直接在命名空间),如在(未标示)回答说这个问题你也许可以用“+”语法来访问在XAML枚举无法找到WPF中静态引用的枚举类型

ConverterParameter = {x:静态局部:YourClass + YourNestedEnumType.Enum1}

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


编辑(2012年1月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;
    }
}

编辑(15年5月7日):

如果为Nullable枚举(问题中要求,但在某些情况下可能需要,例如ORM从数据库返回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);
    }

注意-NullReferenceException(18年10月10日):

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


26
我同意,我相信这是一个更好的解决方案。此外,如果枚举值发生更改,使用此转换将导致项目在编译时而不是运行时中断,这是一个很大的优势。
CrimsonX

4
这肯定是比公认的解决方案更好的解决方案。+1
OrPaz

7
不错的解决方案。我要补充一点,这实际上只是一个比较2个值的比较转换器。它的名称可能比EnumToBooleanConverter更为通用,例如
CompareConverter

5
@斯科特,非常好。无论是否带有Flags属性,此转换器在任何情况下都是很好的。但是在大多数情况下,直接将此过滤器用作枚举为标志的转换器将是愚蠢的。原因是您应该使用先前的值实现布尔calc(| =或^ =)以获得正确的结果,但转换器无法访问先前的值。然后,您应该为每个枚举值添加一个布尔值,并在MVVM模型中自己进行正确的布尔计算。但是感谢您提供的每条信息,非常有用。
埃里克·

2
在Windows Phone 8(可能是Win Store Apps)中,我们没有x:static,因此我们不能在此处直接使用该解决方案。但是,IDE / Complier足够聪明,可以针对所有字符串文字查找字符串(无论如何,我还是这么认为的)。例如,这可以正常工作<RadioButton IsChecked =“ {Binding TrackingMode,ConverterParameter = Driving,Converter = {StaticResource EnumToBooleanConverter},Mode = TwoWay}” />在设计/编译时而不是运行时会捕获Driving中的任何错别字。
2014年

26

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

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


Silverlight中没有任何绑定。仅WPF。请改用null。
亚历山大·瓦西里耶夫

1
绑定.UWP也一无所获。
BlackICE

5

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

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


我使用双向方法使用ListBox和DataTemplate进行绑定,所以应该这样做。
Bryan Anderson

这个错误:geekswithblogs.net/claraoscura/archive/2008/10/17/125901.aspx对我来说毁了一天。
Slampen

3
到目前为止,这是最好的解决方案,其他所有原因都会导致冗余代码。(使用ListBox的另一个示例
HB 2012年

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}"/>

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


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的原因)

假设您在StartTask枚举中有3个项目,例如{Undefined,Manual,Automatic}。您希望默认为Undefined,因为在用户设置一个值之前,它是未定义的。另外:SelectedItem如何处理?您的ViewModel没有SelectedStartTask。
user1040323 '19

在我的ViewModel中,StartUp属性是一个EnumSelection<StartTask>对象。如果查看的定义,则EnumSelection<T>可以看到它具有Value属性。因此,视图模型不需要具有“ SelectedStartTask”。您将使用StartUp.Value。至于默认值Undefined,请参见第3枚举AdditionalOptions,它具有None而不是Undefined,但是您可以将其名称更改为所需的名称。
尼克,

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
我向您表示感谢,感谢您为我所做的帮助。对我来说,这就像是一种魅力。
Elham Azadfar '17

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转换器:

    公共类FlagsEnumToBooleanConverter:IValueConverter {
        private int _flags = 0;
        公共对象Convert(对象值,类型targetType,对象参数,字符串语言){
            if(value == null)返回false;
            _flags =(int)值;
            类型t = value.GetType();
            对象o = Enum.ToObject(t,parameter);
            返回((Enum)value).HasFlag((Enum)o);
        }

        公共对象ConvertBack(对象值,类型targetType,对象参数,字符串语言)
        {
            if(value?.Equals(true)?? false){
                _flags = _flags | (int)参数;
            }
            其他{
                _flags = _flags&〜(int)参数;
            }
            返回_flags;
        }
    }

有人编辑了我的答案以添加带有Flags代码,所以说实话,我从未亲自尝试过/使用过它,并考虑过将其删除,因为我认为将其作为自己的答案更有意义。如果以后能找到我,我可以尝试将一些东西放在一起以测试该代码以及您所拥有的内容,也许可以帮助您找到一个更好的解决方案。
Scott

0

您可以动态创建单选按钮ListBox,无需转换器就可以帮助您做到这一点,非常简单。

创建步骤如下:创建一个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/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.