Qt按钮类04 QRadioButton——大丙+gemini
Qt按钮类04 QRadioButton
https://www.bilibili.com/video/BV1ff4y1C7CK
QRadioButton 是 Qt 框架中专为实现“多选一”互斥选择而设计的单选按钮控件。它与 QPushButton、QToolButton 共同继承自 QAbstractButton 基类。在客户端开发中,单选按钮主要用于表示一组离散且完全互斥的状态选项。
1. 控件继承体系与多态接口
在 Qt 框架设计中,QRadioButton 的类设计极其精简,其核心的状态管理与状态机逻辑主要由其父类 QAbstractButton 托管。(会使用到的方法基本都是继承自父类的)
┌───────────────┐
│ QWidget │
└───────┬───────┘
│
┌───────┴───────┐
│QAbstractButton│
└───────┬───────┘
│
┌───────┴───────┐
│ QRadioButton │
└───────────────┘
1.1 构造函数
根据 Qt 官方 API 文档,QRadioButton 提供了两种显式构造函数:
// 1. 标题构造:直接初始化单选文本并挂载父对象(最常用)
QRadioButton(const QString &text, QWidget *parent = nullptr);
// 2. 空白构造:仅指定父对象,后续通过成员函数配置属性
explicit QRadioButton(QWidget *parent = nullptr);
1.2 常用多态接口(继承自 QAbstractButton)
由于核心状态控制 API 完全托管给基类,在纯代码架构开发中,应当熟练调用以下多态函数:
// 1. 状态写入:显式改变单选按钮的选中状态(常用于初始化默认勾选)
void QWidget::setChecked(bool);
// 2. 状态读取:获取当前单选按钮是否被用户选中
bool QWidget::isChecked() const;
// 3. 互斥托管控制:配置按钮是否参与自动互斥组(QRadioButton 在构造时该属性默认被锁死为 true)
void QAbstractButton::setAutoExclusive(bool);
2. 核心机理剖析:非对称双稳态与隔离分组域
2.1 状态机的非对称双稳态特征
单选按钮的点击自锁机制与具备 checkable 属性的命令按钮(如 QPushButton)存在本质区别:
- 从未选中到选中:点击一个处于弹起状态的单选按钮,状态机翻转,属性由
false变为true。同时,触发级联清空机制,强行将同组内其他所有单选按钮状态洗牌为false。 - 从选中到未选中:点击一个已经处于下沉选中状态的单选按钮,状态机保持死锁,选中状态无法被二次点击取消。若要取消该按钮的选中状态,唯一的途径是点击同组内的其他单选按钮。
2.2 互斥域的物理组件隔离机制
底层的默认规则:Qt 框架内部在判定互斥时,默认会将直接挂载在同一个父窗口(Parent Widget)下的所有单选按钮划归为唯一互斥组。
冲突场景:如果在同一个主窗体内需要并存多套不同的互斥单选逻辑(例如:组 A 选择“调查指标 A”,组 B 选择“调查指标 B”),若不进行干预,整个窗口的所有单选按钮将共享同一个互斥域,导致全界面最终只能有一个按钮被选中。
物理隔离解耦方案:通过引入容器控件(如 QGroupBox 组框、QFrame 框架或通用的 QWidget),为不同的单选组件开辟独立的物理父对象,从而隔离出不同的互斥作用域。
- 将单选按钮 1、2、3 的父对象指定为
GroupBoxA-> 共享互斥域 A。 - 将单选按钮 4、5 的父对象指定为
GroupBoxB-> 共享互斥域 B。
3. 信号交互流向分析
QRadioButton 状态发生翻转时,会沿着底层通道发射两个常用信号。在纯代码架构连接中需要精准选型:
// 1. 物理触发信号
[signal] void QAbstractButton::clicked(bool checked = false);
// 2. 状态机翻转信号
[signal] void QAbstractButton::toggled(bool checked);
clicked(bool)信号的局限性:用户物理点击已选中的单选按钮时,该信号依然会发射,且入参checked恒定为true。在处理高频重复点击时容易造成逻辑冗余。toggled(bool)信号的优势:该信号完美贴合状态机的状态变更。当组内发生交替互斥时,原选中的按钮状态变为未选中,发射toggled(false);新选中的按钮状态变为选中,发射toggled(true)。通过判断if (checked)能够实现精准的控制流拦截,避免冗余响应。
4. 示例代码
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 配置顶级视窗物理尺寸
this->resize(1600, 1000);
// 纯代码架构:手动构建中心承载组件 (Central Widget)
QWidget* centralWidget = new QWidget(this);
this->setCentralWidget(centralWidget);
// 实例化全局水平布局管理器,用于并排容纳两个独立的 QGroupBox
QHBoxLayout* centralLayout = new QHBoxLayout(centralWidget);
// =========================================================================
// 互斥域1:选择最讨厌公司哪一点
// =========================================================================
// 创建第一个独占式分组框 (Group Box 1)
// 父对象指定 this/centralWidget 都一样,后面centralLayout->addWidget()会重置父对象
QGroupBox* groupEnterprise = new QGroupBox("最讨厌公司哪一点?", this);
// 将组1添加到中心组件中(会变更父对象)
centralLayout->addWidget(groupEnterprise);
// 创建组1的内部垂直布局,并显式指定其物理父对象为 groupEnterprise
QVBoxLayout* layoutGroup1 = new QVBoxLayout(groupEnterprise); // 组1垂直内部布局
// 创建单选按钮,显式指定 groupEnterprise 为其直接物理父对象,确保互斥隔离
QRadioButton* radio9117 = new QRadioButton("实施9117常态化加班机制", groupEnterprise);
QRadioButton* radioNosalary= new QRadioButton("公司不发放加班费", groupEnterprise);
QRadioButton* radioNoGirl = new QRadioButton("公司缺乏女性技术人员", groupEnterprise);
// 注入组1的垂直布局管理器中进行排版托管
layoutGroup1->addWidget(radio9117);
layoutGroup1->addWidget(radioNosalary);
layoutGroup1->addWidget(radioNoGirl);
// 显式初始化组1的默认选中状态机
radio9117->setChecked(true);
// =========================================================================
// 互斥域2:选择前台是否好看
// =========================================================================
// 创建第二个独占式分组框 (Group Box 2)
// 父对象指定 this/centralWidget 都一样,后面centralLayout->addWidget()会重置父对象
QGroupBox* groupFrontDesk = new QGroupBox("前台是否好看?", this);
// 将组2添加到中心组件中(会变更父对象)
centralLayout->addWidget(groupFrontDesk);
// 创建并指定布局
QVBoxLayout* layoutGroup2 = new QVBoxLayout(groupFrontDesk);
// 创建单选按钮,显式指定 groupFrontDesk 为父对象,形成第二个独立的互斥作用域
QRadioButton* radioBeautiful = new QRadioButton("前台好看", groupFrontDesk);
QRadioButton* radioUgly = new QRadioButton("前台不好看", groupFrontDesk);
// 注入组2的垂直布局管理器
layoutGroup2->addWidget(radioBeautiful);
layoutGroup2->addWidget(radioUgly);
// 显式初始化组2的默认选中状态机
radioBeautiful->setChecked(true);
// 信号与槽机制等其余部分略...
}
5. 补充说明
5.1 纯代码开发中的“父对象隐式污染”陷阱
1. 纯代码开发中的“所有权隐式污染”陷阱
在纯代码编写 UI 阶段,开发者若习惯性地将单选按钮全部声明为 new QRadioButton("Text", this)(直接挂载到主窗体 this),随后在布局管理时,如果误调用了全局布局的 addWidget,或者未能将其添加至各自 QGroupBox 的内部布局管理器中,这 5 个单选按钮的直接物理父对象依然会被判定为主窗体 this。
这将导致原本设计为相互隔离的两套单选逻辑被强行合并为同一个互斥组,进而引发逻辑故障。控制互斥域的唯一核心标准,是其直接物理父对象(Direct Parent Widget)是否一致。
5.2 破除物理容器排版限制的方案:QButtonGroup
如果产品经理要求多个单选按钮在物理排版上错落分散在窗口的不同角落(无法放进同一个 GroupBox 中),但逻辑上又要求它们必须互斥,此时可以通过引入非可视化逻辑分组类 QButtonGroup 来解决物理容器的限制:
// 在堆区实例化逻辑分组(非可视化组件,仅托管逻辑,不影响 UI 排版)
QButtonGroup *logicStatusGroup = new QButtonGroup(this);
// 将处于不同物理区域的单选按钮注册到同一个逻辑组中
logicStatusGroup->addButton(radioBtnInLeftArea); // 添加左侧区域的单选按钮
logicStatusGroup->addButton(radioBtnInRightArea); // 添加右侧区域的单选按钮
// 此时,即使按钮在物理空间上未处于同一父容器下,其底层状态机也将被锁定在同一个逻辑互斥域内

浙公网安备 33010602011771号