You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF实现:勾选一个复选框时自动取消其余复选框选中状态

实现复选框组的单选效果(避免冗余命令与布尔值)

下面提供三种不同思路的解决方案,都能避免创建多个独立命令和布尔值的繁琐流程:

方案1:用RadioButton伪装成CheckBox(最简便)

RadioButton原生支持单选分组逻辑,只需修改其ControlTemplate让它外观和CheckBox一致,就能直接复用原生功能,完全不需要额外ViewModel代码。

示例XAML:

<Window.Resources>
    <!-- 将RadioButton样式修改为CheckBox外观 -->
    <Style x:Key="CheckBoxStyleRadioButton" TargetType="RadioButton">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="RadioButton">
                    <Grid>
                        <CheckBox x:Name="checkBox" 
                                  IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" 
                                  Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="checkBox" Property="Opacity" Value="0.5"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<!-- 同一GroupName的RadioButton自动互斥 -->
<StackPanel>
    <RadioButton Style="{StaticResource CheckBoxStyleRadioButton}" GroupName="SingleCheckGroup" Content="选项1"/>
    <RadioButton Style="{StaticResource CheckBoxStyleRadioButton}" GroupName="SingleCheckGroup" Content="选项2"/>
    <RadioButton Style="{StaticResource CheckBoxStyleRadioButton}" GroupName="SingleCheckGroup" Content="选项3"/>
    <RadioButton Style="{StaticResource CheckBoxStyleRadioButton}" GroupName="SingleCheckGroup" Content="选项4"/>
    <RadioButton Style="{StaticResource CheckBoxStyleRadioButton}" GroupName="SingleCheckGroup" Content="选项5"/>
</StackPanel>

方案2:ViewModel维护单个选中标识(MVVM友好)

在ViewModel中仅维护一个代表选中项的属性(比如索引、ID或枚举值),通过转换器将每个CheckBox的IsChecked绑定到「当前项是否等于选中标识」,配合单个通用命令处理选中逻辑。

ViewModel代码

public class MainViewModel : INotifyPropertyChanged
{
    // 用索引表示选中项,-1代表未选中
    private int _selectedIndex = -1;
    public int SelectedIndex
    {
        get => _selectedIndex;
        set
        {
            _selectedIndex = value;
            OnPropertyChanged();
        }
    }

    // 通用选中命令
    public ICommand CheckBoxSelectCommand { get; }

    public MainViewModel()
    {
        CheckBoxSelectCommand = new RelayCommand<int>(index => SelectedIndex = index);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

// 基础RelayCommand实现(无框架依赖时使用)
public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Func<T, bool> _canExecute;

    public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
    public void Execute(object parameter) => _execute((T)parameter);
    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

XAML代码

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

<StackPanel>
    <CheckBox Content="选项1" 
              IsChecked="{Binding SelectedIndex, Converter={local:IndexToBoolConverter}, ConverterParameter=0}"
              Command="{Binding CheckBoxSelectCommand}" CommandParameter="0"/>
    <CheckBox Content="选项2" 
              IsChecked="{Binding SelectedIndex, Converter={local:IndexToBoolConverter}, ConverterParameter=1}"
              Command="{Binding CheckBoxSelectCommand}" CommandParameter="1"/>
    <CheckBox Content="选项3" 
              IsChecked="{Binding SelectedIndex, Converter={local:IndexToBoolConverter}, ConverterParameter=2}"
              Command="{Binding CheckBoxSelectCommand}" CommandParameter="2"/>
    <CheckBox Content="选项4" 
              IsChecked="{Binding SelectedIndex, Converter={local:IndexToBoolConverter}, ConverterParameter=3}"
              Command="{Binding CheckBoxSelectCommand}" CommandParameter="3"/>
    <CheckBox Content="选项5" 
              IsChecked="{Binding SelectedIndex, Converter={local:IndexToBoolConverter}, ConverterParameter=4}"
              Command="{Binding CheckBoxSelectCommand}" CommandParameter="4"/>
</StackPanel>

转换器代码

public class IndexToBoolConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is int selectedIdx && parameter is string param && int.TryParse(param, out int targetIdx))
        {
            return selectedIdx == targetIdx;
        }
        return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool isChecked && isChecked && parameter is string param && int.TryParse(param, out int targetIdx))
        {
            return targetIdx;
        }
        return Binding.DoNothing;
    }

    public override object ProvideValue(IServiceProvider serviceProvider) => this;
}

方案3:附加属性封装单选逻辑(解耦View与ViewModel)

创建一个附加属性,给需要互斥的CheckBox设置同一个分组名称,所有单选逻辑都封装在附加属性中,ViewModel无需做任何修改。

附加属性代码

public static class SingleCheckGroup
{
    public static readonly DependencyProperty GroupNameProperty =
        DependencyProperty.RegisterAttached("GroupName", typeof(string), typeof(SingleCheckGroup), new PropertyMetadata(null, OnGroupNameChanged));

    public static string GetGroupName(DependencyObject obj) => (string)obj.GetValue(GroupNameProperty);
    public static void SetGroupName(DependencyObject obj, string value) => obj.SetValue(GroupNameProperty, value);

    private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is CheckBox cb)
        {
            cb.Checked -= OnCheckBoxChecked;
            cb.Checked += OnCheckBoxChecked;
        }
    }

    private static void OnCheckBoxChecked(object sender, RoutedEventArgs e)
    {
        if (sender is CheckBox currentCb && !string.IsNullOrEmpty(GetGroupName(currentCb)))
        {
            var window = Window.GetWindow(currentCb);
            // 查找同组的其他CheckBox并取消选中
            var groupCbs = FindVisualChildren<CheckBox>(window)
                .Where(cb => GetGroupName(cb) == GetGroupName(currentCb) && cb != currentCb);

            foreach (var cb in groupCbs)
            {
                cb.IsChecked = false;
            }
        }
    }

    // 遍历可视化树查找子元素
    private static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
    {
        if (parent == null) yield break;
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is T t) yield return t;
            foreach (var childOfChild in FindVisualChildren<T>(child)) yield return childOfChild;
        }
    }
}

XAML使用代码

<StackPanel>
    <CheckBox Content="选项1" local:SingleCheckGroup.GroupName="MyCheckGroup"/>
    <CheckBox Content="选项2" local:SingleCheckGroup.GroupName="MyCheckGroup"/>
    <CheckBox Content="选项3" local:SingleCheckGroup.GroupName="MyCheckGroup"/>
    <CheckBox Content="选项4" local:SingleCheckGroup.GroupName="MyCheckGroup"/>
    <CheckBox Content="选项5" local:SingleCheckGroup.GroupName="MyCheckGroup"/>
</StackPanel>

内容的提问来源于stack exchange,提问作者Nougat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 20:35:16