Qt Widgets 的按钮基类是 QAbstractButton,实际常用控件包括 QPushButton、QRadioButton、QCheckBox 和 QToolButton。Qt Quick Controls 也有 AbstractButton,但它使用 QML 属性和信号,不能把两个体系的 API 放进同一张表后互相替用。
本文 C++ 示例的 --self-test 使用 assert,应在 Debug 构建或未定义 NDEBUG 的情况下运行;Release 构建若禁用了断言,退出成功不代表这些检查已经执行。
原有类图与查阅范围
跳转到“原有类图与查阅范围”

两张原图保留作分类索引,覆盖范围比按钮更广,也包含不同模块的类。它们不是特定 Qt 新版本全部可用类型的清单;是否安装模块、继承关系和版本变化应查对应版本文档。尤其不能由图中都出现 UI 对象,就推断它们全都继承 QWidget。
Qt Widgets:QAbstractButton 的接口层次
跳转到“Qt Widgets:QAbstractButton 的接口层次”| 分类 | 名称 | 用途与限制 |
|---|---|---|
| 互斥属性 | autoExclusive | 同一父控件下的可选按钮自动互斥;已加入 QButtonGroup 时按组管理 |
| 重复属性 | autoRepeat、autoRepeatDelay、autoRepeatInterval | 按住期间重复触发按钮信号,时间单位毫秒 |
| 状态属性 | checkable、checked、down | 是否可选、是否选中、是否显示按下;三者不是同一状态 |
| 内容属性 | text、icon、iconSize、shortcut | 文字、QIcon、图标尺寸和快捷键 |
| 公共槽 | click()、animateClick() | 立即模拟点击或带按下动画的点击 |
| 公共槽 | setChecked(bool)、toggle()、setIconSize(QSize) | 设置选中、切换状态和图标尺寸 |
| 信号 | clicked(bool)、pressed()、released()、toggled(bool) | 点击、按下、释放、选中变化 |
| 构造与析构 | QAbstractButton(QWidget *)、~QAbstractButton() | 这是抽象基类,不能直接 new QAbstractButton;使用具体子类或实现必要虚函数 |
| 受保护虚函数 | checkStateSet()、hitButton(QPoint)、nextCheckState() | 供自定义子类调整状态处理和命中检测,不能当成外部公共调用 |
| 受保护纯虚函数 | paintEvent(QPaintEvent *) | 自定义绘制入口,具体子类须提供实现 |
属性的访问方式仍分别是 setText()/text()、setIcon()/icon()、setIconSize()/iconSize()、setShortcut()/shortcut()、setAutoExclusive()/autoExclusive()、setAutoRepeat()/autoRepeat()、setAutoRepeatDelay()/autoRepeatDelay()、setAutoRepeatInterval()/autoRepeatInterval()、setCheckable()/isCheckable()、setChecked()/isChecked()、setDown()/isDown()。
程序调用 setChecked() 可能发出 toggled,却不会因此产生一次 clicked。setDown(true) 用于按下状态,也不等于执行一次点击。有关公共槽与受保护虚函数的正式区分,参见 QAbstractButton。
创建按钮、单选按钮与批量设置
跳转到“创建按钮、单选按钮与批量设置”原片段在 YOLOWINDOW 中用 setGeometry(QRect(QPoint(100,100), QSize(200,50))) 放置按钮。这适合演示绝对位置;需要随窗口缩放的界面,通常使用布局。下面补齐 Qt 6 Widgets、C++17 程序,同时保留两个单选按钮、toggled 处理和按对象名启用按钮的用途。
#include <QApplication>#include <QLabel>#include <QPushButton>#include <QRadioButton>#include <QStringList>#include <QVBoxLayout>#include <QWidget>#include <cassert>
static void setAllButtonsEnabled(QWidget &root, bool enabled) { for (auto *button : root.findChildren<QPushButton *>()) button->setEnabled(enabled);}
static void enableOnly(QWidget &root, const QStringList &names) { for (auto *button : root.findChildren<QPushButton *>()) button->setEnabled(names.contains(button->objectName()));}
int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; auto *layout = new QVBoxLayout(&window); auto *first = new QPushButton("My Button", &window); auto *repeat = new QPushButton("Hold to repeat", &window); first->setObjectName("pushButton"); repeat->setObjectName("pushButton_2"); repeat->setAutoRepeat(true); repeat->setAutoRepeatDelay(400); repeat->setAutoRepeatInterval(50); layout->addWidget(first); layout->addWidget(repeat); auto *option1 = new QRadioButton("Option 1", &window); auto *option2 = new QRadioButton("Option 2", &window); layout->addWidget(option1); layout->addWidget(option2); auto *status = new QLabel("Option 1", &window); layout->addWidget(status); for (auto *radio : {option1, option2}) { QObject::connect(radio, &QRadioButton::toggled, &window, [radio, status](bool checked) { if (checked) status->setText(radio->text()); }); } option1->setChecked(true); int repeatCount = 0; QObject::connect(repeat, &QPushButton::clicked, &window, [&](bool) { status->setText(QString::number(++repeatCount)); }); if (app.arguments().contains("--self-test")) { assert(option1->autoExclusive() && option2->autoExclusive()); option2->click(); assert(option2->isChecked() && !option1->isChecked()); assert(status->text() == "Option 2"); assert(repeat->autoRepeat() && repeat->autoRepeatDelay() == 400); assert(repeat->autoRepeatInterval() == 50); setAllButtonsEnabled(window, false); assert(!first->isEnabled() && !repeat->isEnabled()); assert(option2->isEnabled()); enableOnly(window, {"pushButton_2"}); assert(!first->isEnabled() && repeat->isEnabled()); repeat->click(); assert(repeatCount == 1); window.setEnabled(false); repeat->setEnabled(true); assert(!repeat->isEnabled()); return 0; } window.show(); return app.exec();}同一 QWidget 父对象下的 QRadioButton 默认自动互斥;需要多组、跨容器分组或稳定 ID 时,使用 QButtonGroup。这里用 lambda 捕获具体按钮,因此不需要从 sender() 猜测信号来源,也不需要仅为普通成员函数加入 Q_OBJECT。
findChildren<QPushButton *>() 默认递归查找后代,包括 QPushButton 的派生类,不包括 QRadioButton。若只找直接子对象,应传 Qt::FindDirectChildrenOnly;对象名也不是 Qt 强制唯一的 ID。上面的 enableOnly 对所有匹配名字生效,生产界面应保证命名规则明确。禁用共同祖先后,单独对子按钮 setEnabled(true) 不能让它有效;也不要把启用状态代替实际业务权限校验。QObject 子对象查找、QWidget enabled
autoRepeat 保留原例的 400 ms 延迟和 50 ms 间隔,但它表示重复点击,不是新增一个“长按一次”的信号。如果要求只在超过阈值后执行一次,并取消普通短按动作,需要另写计时和取消逻辑。上述 self-test 验证配置及普通程序点击;持续按压的真实事件时序应另测,不能仅因配置值正确就推断所有平台手感相同。
Qt Quick Controls:单独阅读 QML API
跳转到“Qt Quick Controls:单独阅读 QML API”下表保留原第二张表的条目,并标明它属于 QML AbstractButton。此处示例范围是 Qt 6.10;click() 与 animateClick() 从 Qt 6.8 才提供。
| 分类 | 名称 | 正确理解 |
|---|---|---|
| 信号 | canceled()、clicked()、doubleClicked() | 取消、点击、双击 |
| 信号 | pressAndHold()、pressed()、released() | 长按、按下、释放;autoRepeat 开启时不发 pressAndHold |
| 信号 | toggled() | 无 bool 参数;处理器内读 checked,不能照抄 Widgets 的 toggled(bool) |
| 方法 | click()、animateClick()、toggle() | 前两者模拟点击,toggle 改变选中状态 |
| 属性 | action、autoExclusive | 关联 QML Action、同父项的自动互斥 |
| 属性 | autoRepeat、autoRepeatDelay、autoRepeatInterval | 是否重复、初次延迟、间隔 |
| 属性 | checkable、checked、down、text | 可选中、选中、视觉按下和文字 |
| 属性 | display、icon | 控制文字/图标显示方式及图标组合 |
| 图标属性 | icon.cache、icon.color、icon.height、icon.width、icon.name、icon.source | 缓存、颜色、尺寸、主题名称和 URL |
| 指示器 | indicator、implicitIndicatorHeight、implicitIndicatorWidth | 指示器 Item 与其隐含尺寸 |
| 只读状态 | pressX、pressY、pressed | 最近按下坐标及当前物理按下状态,不能写 myButton.pressed = true |
QML 的 onToggled 用于交互切换;需要响应 checked 属性的所有变化时用 onCheckedChanged。视觉 down 和物理 pressed 分开处理,原表的 pressed 赋值示例无效。参见 AbstractButton QML Type。
保存以下两个文件到同一个目录。MyButton.qml 定义组件:
import QtQuickimport QtQuick.Controls
Button { id: control objectName: "customButton" checkable: true text: checked ? "Selected" : "Select" onToggled: console.log("checked:", checked)}main.qml 使用该组件:
import QtQuickimport QtQuick.Controls
ApplicationWindow { width: 360 height: 200 visible: true title: "QML button" MyButton { anchors.centerIn: parent }}这些是普通 .qml 文件,可以由 Qt QML 运行器或项目加载。原命令 uic -o output.ui.qml input.ui 不会把 Widgets 界面转换为 QML;uic 读取 Widgets .ui XML 并生成相应代码,给输出文件换后缀不会改变界面体系。Qt Design Studio 的 .ui.qml 还带有设计工具的声明式格式约束,不应把含任意脚本的普通 QML 文件直接改名后等同使用。uic
Designer 的 Promote to:基类必须真实匹配
跳转到“Designer 的 Promote to:基类必须真实匹配”提升控件用于让 Widgets Designer 中的占位控件在构建时成为自定义派生类,不会把控件变成 QML。步骤是先实现子类,再放置兼容基类控件,填写 Promoted class name 与真实头文件路径,Add 后 Promote。头文件名区分大小写的平台尤其需要严格匹配;项目也必须编译并链接子类实现。

原文字用从 QTableWidget 继承的 Spreadsheet 举例,但截图的 Base class name 实际是 QTableView。若 Spreadsheet 继承 QTableWidget,就放置并提升 QTableWidget;若继承 QTableView,就按截图选 QTableView。不能只复制类名与头文件而忽略基类差异。子类通常需要接受 QWidget *parent 的构造方式。Designer 在没有自定义插件时显示的仍主要是占位基类外观,运行时才体现子类行为。Designer 自定义控件