如何在 WPF (MVVM) 中取消选中单选按钮

12

我有一个单选按钮组。选择填写表格不是强制性的。一开始所有单选按钮都未被选中。如果用户无意中点击其中一个,他将无法回到最初状态,因为至少必须选中一个。

那么,我该如何取消单选按钮的选择,而不强迫用户做出不想要的选择?

附:表格是在运行时构建的,并且我正在遵循MVVM设计模式。对于必填项,单选按钮方案非常适合,并且我已经在这种情况下使用了它。

12个回答

18

试试这个:

public class OptionalRadioButton : RadioButton
{
    #region bool IsOptional dependency property
    public static readonly DependencyProperty IsOptionalProperty = 
        DependencyProperty.Register(
            "IsOptional", 
            typeof(bool), 
            typeof(OptionalRadioButton), 
            new PropertyMetadata((bool)true,
                (obj, args) =>
                {
                    ((OptionalRadioButton)obj).OnIsOptionalChanged(args);
                }));
    public bool IsOptional
    {
        get
        {
            return (bool)GetValue(IsOptionalProperty);
        }
        set
        {
            SetValue(IsOptionalProperty, value);
        }
    }
    private void OnIsOptionalChanged(DependencyPropertyChangedEventArgs args)
    {
        // TODO: Add event handler if needed
    }
    #endregion

    protected override void OnClick()
    {
        bool? wasChecked = this.IsChecked;
        base.OnClick();
        if ( this.IsOptional && wasChecked == true )
            this.IsChecked = false;
    }
}

2
这很棒。如果您使用它,请确保只在其自己的cs文件中创建它,不要尝试创建一个带有自己XAML的新UserControl。 - Jonathan Tuzman

10

个人使用 ListBox,并重写模板使用 RadioButtons 实现此行为。

ListBox 是最适合完成以下所有任务的控件:

  • 显示项目列表
  • 一次只能选择一个项目,因此数据模型中只维护一个属性
  • 用户可以将选择的项目保留为空,表示未选择任何项目

我为 ListBox 自定义了样式,去除了边框和背景颜色,并使用 RadioButton 逐个绘制每个项目,其中 IsChecked 绑定到 ListBoxItem.IsSelected。通常是这样的:

<Style x:Key="RadioButtonListBoxStyle" TargetType="{x:Type ListBox}">
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}" >
                <Setter Property="Margin" Value="2, 2, 2, 0" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Border Background="Transparent">
                                <RadioButton
                                    Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
                                    IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

而要显示单选按钮本身,通常很简单,就像这样:

<ListBox ItemsSource="{Binding AvailableValues}"
         SelectedValue="{Binding SelectedValue}"
         Style="{StaticResource RadioButtonListBoxStyle}" />

1
不错,Rachel,我喜欢你的解决方案。 - Bobby

7
我用了一些事件处理程序来实现这个场景。
<RadioButton Checked="RB_Checked" Click="RB_Clicked"/>

在XAML的Codebehind中:
Private JustChecked as Boolean

Private Sub RB_Checked(sender As Object, e As RoutedEventArgs)
    Dim s As RadioButton = sender
    ' Action on Check...
    JustChecked = True
End Sub

Private Sub RB_Clicked(sender As Object, e As RoutedEventArgs)
    If JustChecked Then
        JustChecked = False
        e.Handled = True
        Return
    End If
    Dim s As RadioButton = sender
    If s.IsChecked Then s.IsChecked = False        
End Sub

或者在C#中。
private bool JustChecked;
private void RB_Checked(object sender, RoutedEventArgs e)
{
    RadioButton s = sender;
    // Action on Check...
    JustChecked = true;
}

private void RB_Clicked(object sender, RoutedEventArgs e)
{
    if (JustChecked) {
        JustChecked = false;
        e.Handled = true;
        return;
    }
    RadioButton s = sender;
    if (s.IsChecked)
        s.IsChecked = false;
}

点击事件在选中事件之后触发。


Peter Moore的解决方案不需要变量。 - AL - Lil Hunk
大多数情况下,实际上在Peter Moore的解决方案中也使用了相同的变量:bool? wasChecked = this.IsChecked;。虽然不确定为什么变量的使用很重要。我认为Peter Moore的解决方案更优雅,因为它不需要代码后台,但这是一个更简单和直接的解决方案。 - jumbo

3
“dirty way” 的做法是创建折叠的单选按钮。
<StackPanel>
    <RadioButton Content="Option 1" GroupName="optionSet1" PreviewMouseDown="RadioButton_OnPreviewMouseDown"/>
    <RadioButton Content="Option 2" GroupName="optionSet1" PreviewMouseDown="RadioButton_OnPreviewMouseDown"/>
    <RadioButton GroupName="optionSet1" x:Name="rbEmpty" Visibility="Collapsed" />
</StackPanel>

如果用户单击已经选中的 RadioButton,则勾选空的 RadioButton。

代码后台

private void RadioButton_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        var radioButton = sender as RadioButton;
        if (radioButton.IsChecked.GetValueOrDefault())
        {
            rbEmpty.IsChecked = true;
            e.Handled = true;
        }
    }

2
你的单选按钮:

<RadioButton x:Name="MyRadioButton">Some Radio Button</RadioButton>

代码后台:

 MyRadioButton.IsChecked = false;

如果您想使用绑定来取消选中它,请执行以下操作:

代码后台:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = this;

    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    #endregion


    private bool _isRadioChecked;

    public bool IsRadioChecked
    {
        get
        {
            return _isRadioChecked;
        }
        set
        {
            _isRadioChecked = value;
            OnPropertyChanged("IsRadioChecked");
        }
    }
}

XAML(视图):
<RadioButton IsChecked="{Binding IsRadioChecked}">Some Radio Button</RadioButton>

2
如果您正在使用MVVM,我刚刚在绑定到每个单选按钮的IsChecked属性的属性的setter中进行了一些处理。这基本上确保当用户单击radiobtn选项时,IsChecked值将设置为先前的相反值。以下是我的代码示例:
private bool _rdoIsChecked;
    public bool RdoIsChecked
    {
        get { return _rdoIsChecked; }
        set { SetProperty(ref _rdoIsChecked, !_rdoIsChecked);}

    }

1

嗨,这是如何在每次单击时检查和取消选中单选框的方法。

  int click = 0;
    private void RadioButton_Click(object sender, RoutedEventArgs e)
    {
        click++;
        ((RadioButton)sender).IsChecked = click % 2 == 1 ;
        click %= 2;
    }

0
在这种情况下,你不应该使用“单选按钮”(RadioButton)。这个控件的目的是始终只有一个被选中。在你的场景中,我更喜欢使用“复选框”(CheckBox)。

2
注意:使用复选框时,您可以同时选择多个选项,但单选按钮不行。 - Fabio Salvalai
@Tomtom 我不同意,因为选择应该只有一个(典型的单选按钮选择),但回答并非强制性的。 - overcomer
1
没有帮助。此外,需要勾选一个和仅需勾选一个之间存在差异,还有只能勾选一个或不勾选。 - Emperor Eto

0

你可以像@Tomtom指出的那样使用复选框,但这也会给用户同时选择多个选项的机会,或者你需要实现一个复杂的机制来确保一次只能选择一个选项。

另一个非常便宜的解决方法是在单选按钮选项中添加一个额外的条目。

你是怎么知道我们的?

  • 谷歌
  • 朋友介绍
  • 电视广告
  • 其他

问题在于我不想强制用户做出非必要的选择。 - overcomer
你不必强制他们。如果他一开始没有做出选择,那么它将不会被执行。另一个选项是在代码后台设置一个“重置表单”按钮并取消选择。 - Fabio Salvalai
首先,想象一下您希望用户界面如何运作,形成一个心理画面:如果用户不打算选择某个选项,您希望他如何撤销选择?一旦您回答了这个问题,我们就可以进一步帮助您 :) - Fabio Salvalai
是的,我考虑过添加一个重置按钮,但表单可能很大,而且我认为重置整个表单不是一个好选择。 - overcomer
然后有一个按钮来重置您的单选按钮,但这似乎不是很好的用户体验。您首先必须解决您的用户体验问题 :) - Fabio Salvalai

0

我同意Tomtom的观点,RadioButton可能不是最好的设计选择,但如果该选择不在您的控制范围内或出于其他原因必须这样做,您将需要一些机制来强制取消选择。

以下是一些可能性:

  • RadioButton组附近提供一个清除按钮
  • 如果再次单击已选中的RadioButton,则取消选择它。
  • 当右键单击选中的RadioButton时,提供上下文菜单清除选项

由于表单是在运行时构建的,因此您需要在创建控件时附加适当的事件处理程序,如下所示:

   // Create an instance of the control
   var controlType = typeof(Control).Assembly.GetType(fullyQualifiedControl, true);
   var control = Activator.CreateInstance(controlType);

    switch (fullyQualifiedControl)
    {
       case "System.Windows.Controls.RadioButton":
          ((RadioButton)control).Checked += new RoutedEventHandler(DataDrivenControl_Checked);
          ((RadioButton)control).Unchecked += new RoutedEventHandler(DataDrivenControl_UnChecked);
          break;
    }

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