Qt按钮类05 QCheckBox——大丙+gemini

Qt按钮类05 QCheckBox——大丙+gemini

https://www.bilibili.com/video/BV1ff4y1C7CK

QCheckBox 是 Qt 框架中用于实现“多选多”逻辑的复选框按钮控件。与 QRadioButton 的排他性互斥机制(“多选一”)不同,QCheckBox 允许在同一个容器作用域内同时选中多个选项,且支持通过二次点击反转或取消选中状态。

在底层设计上,它同样继承自 QAbstractButton。在实际工程中,它常用于多项复合配置开关,或以联动的方式构建具备三态(Tristate)控制逻辑的树状层次结构。

1. 控件继承体系与多态接口

				  ┌───────────────┐
                  │    QWidget    │
                  └───────┬───────┘
                          │
                  ┌───────┴───────┐
                  │QAbstractButton│
                  └───────┬───────┘
                          │
                  ┌───────┴───────┐
                  │   QCheckBox   │
                  └───────────────┘

1.1 显式构造函数

在纯代码编写阶段,QCheckBox 提供了两个标准的显式构造接口:

// 1. 标题构造:初始化复选框文本并挂载物理父对象
QCheckBox(const QString &text, QWidget *parent = nullptr);

// 2. 空白构造:仅指定父对象,属性后续通过属性函数异步配置
explicit QCheckBox(QWidget *parent = nullptr);

1.2 三态(Tristate)管理核心 API

默认情况下,QCheckBox 仅在两种状态间发生状态机翻转。若要使其支持半选中状态(常用于树状结构的父节点),必须显式使能三态属性:

// 1. 属性使能:配置当前复选框是否开启三态机制(默认值为 false)
void setTristate(bool y = true);

// 2. 属性读取:查询当前复选框是否为三态复选框
bool isTristate() const;

1.3 状态读写多态接口(基于 Qt::CheckState)

对于普通双稳态复选框,可直接调用基类的 setChecked(bool)isChecked()。而一旦使能了三态机制,则必须采用更为精确的枚举读写接口:

// 1. 状态写入:显式将复选框驱动至特定的三态位置
void setCheckState(Qt::CheckState state);

// 2. 状态读取:获取当前复选框的确切逻辑状态
Qt::CheckState checkState() const;

Qt::CheckState 状态枚举模型

枚举常量定义 物理数值 界面视觉表现与工程含义
Qt::Unchecked 0 未选中状态:物理控件框内完全留白,表示选项未生效。
Qt::PartiallyChecked 1 半选中状态:物理控件框内填充方块(或横线),表示下属子节点处于“部分选中”的分流状态。
Qt::Checked 2 选中状态:物理控件框内显示勾选标记,表示选项完全生效。

1.4 状态机级联信号

// 核心通知信号:当控件的逻辑 CheckState 发生任何洗牌或变更时,该信号即被底层发射
[signal] void QCheckBox::stateChanged(int state);

注意:由于历史架构原因,该信号发射时附带的入参为 int 类型。在槽函数内进行控制流研判时,应当将其显式或隐式与 Qt::CheckState 枚举常量进行比对。

2. 核心深度机理:多路复选树的联动与控制流拓扑

在纯代码构建具有层级关系的复选树(如“父节点 - 子节点群”)时,界面图纸的删除要求我们必须在底层构建清晰的逻辑拓扑。树状级联联动由两个互为逆向的控制流回路构成:

2.1 正向驱动流:父控子(Top-Down Control Loop)

当用户物理触发根节点(父复选框)时,状态机产生确定性的翻转(要么全选,要么全不选)。此时,系统应当拦截该物理点击事件,强行将下属的所有子节点的状态级联修改为与父节点相同的状态。这一过程称为正向状态广播

2.2 逆向推导流:子反馈父(Bottom-Up Evaluation Loop)

当外围用户逐个点击底层的子节点时,任何一个子节点的状态变更都会向上发射信号。父节点通过实时收集并评估所有活跃子节点的物理勾选状态,进行条件判定:

  • 已选子节点数 == 子节点总数 $\rightarrow$ 父节点逆向置位为 Qt::Checked
  • 已选子节点数 == 0 $\rightarrow$ 父节点逆向置位为 Qt::Unchecked
  • $0 <$ 已选子节点数 $<$ 子节点总数 $\rightarrow$ 父节点逆向置位为 Qt::PartiallyChecked

3. 示例代码

3.1 独立类头文件声明 (evasystemwidget.h)

#pragma once

#include <QWidget>
#include <QList>

class QCheckBox; // 前向声明,规避头文件交叉编译污染

/**
 * @brief NERV 人类补完计划中央推进域独立控制组件
 * @note 采用纯代码架构设计,完全脱离 MainWindow 依赖,可作为独立模块挂载至任意视窗
 */
class EvaSystemWidget : public QWidget
{
    Q_OBJECT

public:
    explicit EvaSystemWidget(QWidget *parent = nullptr);
    ~EvaSystemWidget() override = default;

private slots:
    /**
     * @brief 拦截并处理任意 NERV 成员状态(同步率)变更的通用逆向槽函数
     * @param state 触发该次变更的子节点当前整型状态
     */
    void onNervStateChanged(int state);

private:
    // 根节点(初号机驾驶员-碇真嗣,核心状态驱动/观测器)
    QCheckBox* m_shinjiIkari = nullptr;

    // 子节点群容器(NERV 核心成员链表,解耦硬编码限制)
    QList<QCheckBox*> m_nervMemberList;
};

3.2 独立类源文件实现 (evasystemwidget.cpp)

#include "evasystemwidget.h"
#include <QVBoxLayout>
#include <QGroupBox>
#include <QCheckBox>

EvaSystemWidget::EvaSystemWidget(QWidget *parent)
    : QWidget(parent)
{
    // 1. 纯代码构建局部核心排版布局管理器
    QVBoxLayout* externalLayout = new QVBoxLayout(this);
    
    // 2. 实例化主题复合组件域
    QGroupBox* evaGroup = new QGroupBox("NERV · 人类补完计划中央推进域", this);
    externalLayout->addWidget(evaGroup);

    // 3. 组内内部垂直排版管理器配置
    QVBoxLayout* groupLayout = new QVBoxLayout(evaGroup);

    // 4. 实例化根节点(Shinji),并强行开启三态属性
    m_shinjiIkari = new QCheckBox("Shinji (碇真嗣) [初号机核心觉醒观测器]", evaGroup);
    m_shinjiIkari->setTristate(true);
    groupLayout->addWidget(m_shinjiIkari);

    // 5. 纯代码批量实例化子节点(极简罗马名),并追加至托管容器中
    QStringList nervNames = {
        "Rei (绫波丽) [零号机]", 
        "Asuka (明日香) [二号机]", 
        "Mari (玛丽) [八号机]", 
        "Kaworu (渚薰) [六号机]", 
        "Misato (葛城美里) [战术指挥官]"
    };
    
    for (const QString& name : nervNames)
    {
        QCheckBox* subBox = new QCheckBox(name, evaGroup);
        groupLayout->addWidget(subBox);
        m_nervMemberList.append(subBox); // 建立物理拓扑映射
    }

    // =========================================================================
    // 控制流拓扑 1:正向驱动流 (Top-Down) -> 真嗣核心觉醒,强制重刷全员状态
    // =========================================================================
    connect(m_shinjiIkari, &QCheckBox::clicked, this, [this](bool checked) {
        // 当用户物理点击根节点时,驱动所有 NERV 成员的状态发生强制硬洗牌
        // checked == true  -> 开启人类补完计划,全员强制进入补完状态
        // checked == false -> 中止补完计划,全员回归独立物理个体
        Qt::CheckState targetState = checked ? Qt::Checked : Qt::Unchecked;
        
        for (QCheckBox* subBox : m_nervMemberList)
        {
            // 写入属性时自动触发子节点的 stateChanged 信号,激活逆向控制流逻辑
            subBox->setCheckState(targetState);
        }
    });

    // =========================================================================
    // 控制流拓扑 2:逆向反馈流 (Bottom-Up) -> 各成员的同步率动态反馈至真嗣的状态机
    // =========================================================================
    for (QCheckBox* subBox : m_nervMemberList)
    {
        connect(subBox, &QCheckBox::stateChanged, this, &EvaSystemWidget::onNervStateChanged);
    }
    
    // 默认初始化界面:日常静默状态
    m_shinjiIkari->setCheckState(Qt::Unchecked);
}

void EvaSystemWidget::onNervStateChanged(int /*state*/)
{
    // 实时通过物理迭代法统计当前进入补完状态的成员数,彻底规避传统计数器错位的边界漏洞
    int activatedCount = 0;
    for (const QCheckBox* subBox : m_nervMemberList)
    {
        if (subBox->isChecked())
        {
            activatedCount++;
        }
    }

    // 执行逆向推导控制流判定(根据 NERV 成员激活规模,洗牌碇真嗣的状态机)
    if (activatedCount == m_nervMemberList.size())
    {
        // 边界条件 A:全员参与补完 -> 真嗣进入完全觉醒/引发冲击状态(Checked)
        m_shinjiIkari->setCheckState(Qt::Checked);
    }
    else if (activatedCount == 0)
    {
        // 边界条件 B:全员处于日常静默 -> 真嗣进入平稳/拒绝补完状态(Unchecked)
        m_shinjiIkari->setCheckState(Qt::Unchecked);
    }
    else
    {
        // 边界条件 C:仅部分成员激活同步率 -> 处于补完中转的半选中混沌态(PartiallyChecked)
        m_shinjiIkari->setCheckState(Qt::PartiallyChecked);
    }
}

3.3 模块挂载:如何在主窗体中纯代码调用此独立类

由于该自定义组件独立继承自 QWidget,其解耦度极高。若要在 MainWindow 中展现,只需实例化该类并在布局管理器中进行托管挂载即可:

// mainwindow.cpp 纯代码挂载机制说明
#include "mainwindow.h"
#include "evasystemwidget.h"
#include <QHBoxLayout>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->resize(900, 700);

    // 实例化中心主承载组件
    QWidget* centralWidget = new QWidget(this);
    this->setCentralWidget(centralWidget);

    // 构建全局主布局
    QHBoxLayout* mainLayout = new QHBoxLayout(centralWidget);

    // 纯代码实例化并挂载独立封装的 EVA 控制域组件
    EvaSystemWidget* evaSystem = new EvaSystemWidget(centralWidget);
    
    // 塞入主布局管理器进行排版托管
    mainLayout->addWidget(evaSystem);
}
posted @ 2026-06-15 20:01  学习笔记草稿存放账号  阅读(1)  评论(0)    收藏  举报