WPF单选按钮(两个)(绑定到布尔值)

52

我有一个布尔类型的属性,用复选框表示。

我想将其更改为两个单选按钮,绑定在同一属性上,分别表示 true/false 的值。

如何实现?

10个回答

92
<RadioButton GroupName="Group1" 
             IsChecked="{Binding PropertyValue}" Content="Yes" />
<RadioButton GroupName="Group1"  Content="No" 
             IsChecked="{Binding PropertyValue, 
                         Converter={StaticResource BoolInverterConverter}}" />
public class BoolInverterConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        if (value is bool)
        {
            return !(bool)value;
        }
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
        if (value is bool)
        {
            return !(bool)value;
        }
        return value;
    }

    #endregion
}

32
出于某种原因,我必须使用两个不同的 GroupNames。否则,即使单选按钮似乎表现正常,ViewModel 中的布尔值可以设置为 true,但永远不会重置为 false。 - Qwertie
嗨,您忘记了使用“/”来关闭第二个单选按钮。 - David Weinberg
1
我将这个放在子窗口中。当我第一次打开子窗口时,它很好。但是当我关闭并第二次打开子窗口时,我会得到堆栈溢出异常,因为转换器执行了无限次数。 - Ziggler
同意Qwertie和其他人的观点,尝试了很多次,但是不起作用--逻辑看起来是正确的,但是由于绑定被执行,按钮检查状态没有被设置以反映它。 - J S
如果两个单选按钮都去掉GroupName属性,这个解决方案就可以工作。GroupName属性会导致相互触发,从而导致堆栈溢出。我花了几天时间找到了根本原因。 - user2541063
显示剩余3条评论

20

标准绑定方法的不幸副作用是每当UI被加载时,会触发绑定setter为"未选择"。因此,如果您有处理用户在bool setter中点击的代码,它将做一些奇怪的事情,例如触发setter为“false”,即使您已将其绑定到“true”的bool。

我通过专门用于单选按钮的转换器解决了这个问题:

public class BoolRadioConverter : IValueConverter
{
    public bool Inverse { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool boolValue = (bool) value;

        return this.Inverse ? !boolValue : boolValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool boolValue = (bool)value;

        if (!boolValue)
        {
            // We only care when the user clicks a radio button to select it.
            return null;
        }

        return !this.Inverse;
    }
}

在您的资源中:

<converters:BoolRadioConverter x:Key="BoolRadioConverter" />
<converters:BoolRadioConverter x:Key="InverseBoolRadioConverter" Inverse="True" />

在你的 XAML 中:

<RadioButton
    Content="True option"
    GroupName="radioGroup1"
    IsChecked="{Binding MyProperty,
                        Converter={StaticResource BoolRadioConverter}}" />
<RadioButton
    Content="False option"
    GroupName="radioGroup2"
    IsChecked="{Binding MyProperty,
                        Converter={StaticResource InverseBoolRadioConverter}}" />

太棒了!对我的问题起到了预期的作用。我将其复制以备将来在我的博客上使用。 - EvilInside
如果我将其称为 InverseBoolRadioConverter,我可以忽略 Inverse 变量,并返回 true,因为我总是将其用作单选按钮的转换器。 - Maxoizs
我发现你的代码最适用于可空布尔值,但你可以调整转换器中的if条件来处理非可空值:如果(!boolValue && Nullable.GetUnderlyingType(targetType) != null) - YeahStu
谢谢!我遇到了同样的问题,很高兴找到了你的解决方案。 - Александр Пекшев
在我意识到单选按钮需要位于不同的组中时,它对我很有效。 - tom2051

11
你可以使用一个值转换器来反转布尔值:
使用该转换器,将一个复选框的IsChecked属性绑定到布尔值上,而另一个复选框的IsChecked属性则使用该转换器绑定。这样就可以实现目的了。
以下是这种转换器的代码。我从这里复制了它,并添加了一些代码行。你可以在那里找到更多信息。
public class BoolToOppositeBoolConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter,System.Globalization.CultureInfo culture) {
        if (targetType != typeof(bool)) {
            throw new InvalidOperationException("The target must be a boolean");
        }
        if (null == value) {
            return null;
        }
                    return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter,System.Globalization.CultureInfo culture) {
        if (targetType != typeof(bool)) {
            throw new InvalidOperationException("The target must be a boolean");
        }
        if (null == value) {
            return null;
        }
        return !(bool)value;
    }
} 

要使用它,请在资源部分声明。

 <local:BoolToOppositeBoolConverter x:Key="BoolToOppositeBoolConverter_ValueConverter"/>

然后将其作为静态资源在绑定中使用:

<CheckBox IsChecked="{Binding YourProperty}" />
<CheckBox IsChecked="{Binding YourProperty,Converter={StaticResource BoolToOppositeBoolConverter_ValueConverter}}" />
请注意,该转换器只是一个简单的示例。如果您想在生产代码中使用它,请将其实现得更加优美。我没有测试过它,如果它不能正常工作,请留言。

1
谢谢!我已经使用了Ragunathan的解决方案,但两个都很好。特别感谢您的解释和投入的时间。我正在慢慢地深入学习WPF... - no9

5
如果您将两个单选按钮的 GroupName 属性设置为相同的值(因此只能同时选中一个),则可以在没有转换器的情况下实现此操作。然后,将一个单选按钮的 IsChecked 设置为“True”,并将另一个单选按钮的 IsChecked 绑定到您的布尔变量。切换单选按钮将更改布尔值,但将布尔值更改为 False 不会选择另一个单选按钮。
谢谢, Vlad

1
如果您多次显示对话框,此方法会产生间歇性的绑定副作用。我能够通过在关闭对话框之前清除数据上下文属性来解决这个问题,因此我认为这是一个框架问题。无论如何,我提出了一个更好的解决方案,支持布尔值和枚举,并且在设置或不设置GroupName属性时都可以正常工作。 - votrubac
这是最简单和最容易的解决方案! - Vincent

3

2

当使用MVVMLight时,如果在XAML中设置了DataContext

DataContext="{Binding <Your ViewModel property name>, Source={StaticResource Locator}}" 
BoolInverterConverter会导致窗口第二次打开时出现Stack Overflow错误。
解决方法是在XAML中删除DataContext,并在InitializeComponent()后在窗口构造函数中使用代码进行操作。
DataContext = ServiceLocator.Current.GetInstance<Your View Model class>();

经过一些测试,发现这还不够-在点击单选按钮时可能会随机弹出“堆栈溢出”错误。对我有效的解决方案是:不要使用转换器,而是使用组中其他单选按钮的另一个属性:

public bool Is9to1 { get; set; }
public bool Is1to9 { get { return !Is9to1; } set { Is9to1 = !value; } } 

在XAML中:
   <RadioButton GroupName="Is9to1" IsChecked="{Binding Is1to9}"/>
   <RadioButton GroupName="Is9to1" IsChecked="{Binding Is9to1}"/>

这里也有同样的问题。我对WPF非常陌生,所以我停止了研究为什么会发生这种情况。最终,我使用了一个自定义转换器和XAML中的“CommandParameter”来避免SOE。例如,“Yes”选项与“CommandParameter = 1”,“No”选项与“CommandParameter = 2”。 - Zen

0
Mr-RandomQC的答案来看,一切都正常。但是当我点击同一组中的另一个单选按钮时,相反的单选按钮会显示一个红色框。

我稍微修改了他的代码,将返回Binding.DoNothing而不是像这样返回null

public class BoolRadioConverter : IValueConverter
{
    public bool Inverse { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool boolValue = (bool)value;

        return this.Inverse ? !boolValue : boolValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool boolValue = (bool)value;

        if (!boolValue)
        {
            // Return Binding.DoNothing instead of Return null
            return Binding.DoNothing;
        }

        return !this.Inverse;
    }
}

0

如果您想使布尔值可为空(适用于无默认值/选中的单选按钮),这是对RandomEngy答案的小升级

public class BoolRadioConverter : IValueConverter
{
    public bool Inverse { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool? boolValue = (bool?)value;

        return this.Inverse ? !boolValue : boolValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool? boolValue = (bool?)value;

        if (boolValue != null && (bool)!boolValue)
        {
            // We only care when the user clicks a radio button to select it.
            return null;
        }

        return !this.Inverse;
    }
}

其余部分与他的答案相同。


0

ragunathananswer的简化版本。

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) =>
    Convert(value);

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) =>
    Convert(value);

private object Convert(object value) => 
    value is bool ? !(bool)value : value;

0

回答晚了,但我们多年来一直这样做,首先是使用 WPF,现在是使用 WinUI 3

<StackPanel>

<TextBlock Text="Payment terms" />

<RadioButton
    x:Name="rdo_on_account"
    Content="Account"
    GroupName="account"
    IsChecked="{x:Bind ViewModel.IsOnAccount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

<RadioButton
    Content="Cash"
    GroupName="account"
    IsChecked="{Binding ElementName=rdo_on_account, Path=IsChecked, Converter={StaticResource BoolInvert}}" />
    
</StackPanel>



public class BoolInvert : IValueConverter
{
// convert the value to the destination format
public object Convert(object value, Type targetType, object parameter, string language)
{
    if (value is bool)
    {
        if ((bool)value == true)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    //not a bool
    return value;
}

// convert from the destination format to the source format
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
    return value;
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接