Qt常用控件学习路线:理解对象树与信号槽,手写界面代码

📅 发布时间:2026/9/4 1:11:00
Qt常用控件学习路线:理解对象树与信号槽,手写界面代码
重学 Qt 的第二站几乎都会落在常用控件上。Qt 自带的控件种类远不止 QPushButton、QLabel 这几样但如果只是逐个控件试属性学完两周后很容易发现自己只是记住了 API 名遇到一个带输入框、下拉框和表格的窗口仍然不知道代码该怎么组织。这里按“从零一行行写代码”的学习路线把 Qt 常用控件这条线整理出来先弄懂控件所在的对象树和信号槽是怎么工作的再搭建一个最小 Qt Widgets 工程然后把章节里 14 讲的内容拆成几组最后用一个能编译、能运行、能继续扩展的客户登记窗口把常用控件串起来。读完并按示例操作后你能独立完成三类事情第一新建一个普通 Qt Widgets 工程并手写界面代码而不是只会在 Designer 里拖控件第二知道 QLabel、QPushButton、QLineEdit、QComboBox、QTableWidget 这类高频控件的常用接口以及“用户操作后如何拿到值”第三遇到按钮没反应、控件布局乱、运行时报平台插件错误这类问题能按日志和现象定位根因。1. 别急着背控件先把对象树和信号槽这两条主线打通1.1 控件本质QWidget、QObject 与对象树Qt Widgets 模块里几乎所有可见控件都继承自 QWidget。QWidget 又继承自 QObject所以控件不是一堆互不相干的 C 类而是挂在一棵对象树上的节点。这里的“父子关系”有两层含义。第一层是窗口包含关系子控件默认显示在父控件的客户区里父控件移动、隐藏、销毁时子控件跟着移动、隐藏、销毁。第二层是内存释放关系QObject 的析构函数会递归删除所有子对象。也就是说一个挂到父对象上的 QPushButton通常不需要手动 delete父对象销毁时会统一处理。理解这一点对写代码影响很大。一个典型错误写法是QPushButton *button new QPushButton; // 没有父对象也没有布局管理 button-show();这个按钮虽然会被显示但如果不把指针保存起来后续既拿不到它也容易忘记释放程序结束时变成野内存或泄漏。推荐做法是创建时直接指定父对象或者把它交给布局管理QWidget window; QPushButton *button new QPushButton(QStringLiteral(确定), window);这里把window传进构造函数的parent参数按钮就成为 window 的子控件。当 window 离开作用域析构时Qt 会先析构子对象。需要注意的是如果用栈对象作为父对象子对象必须使用堆对象并且父对象析构顺序要晚于子对象使用结束否则会出现悬空访问。对象树的另一层作用是分类管理。单个窗口里可以有 QGroupBox、QTabWidget、QStackedWidget 这样的容器控件容器也是一层层嵌套的 QWidget 节点。学习常用控件时先看控件在对象树里的位置再看它有多少属性和接口能减少很多混乱。1.2 控件之间靠信号槽不靠互相保存指针控件之间要做交互最常见的手段是信号与槽。信号表示“某件事发生了”槽表示“某件事要执行”QObject::connect 负责把两者连接起来。Qt 5 之后推荐使用的是“函数指针”写法编译期就能检查连接是否成立#include QApplication #include QWidget #include QLabel #include QPushButton int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle(QStringLiteral(信号槽最小示例)); window.resize(400, 300); QLabel *label new QLabel(QStringLiteral(标签), window); label-setGeometry(150, 80, 100, 30); QPushButton *button new QPushButton(QStringLiteral(清空文本), window); button-setGeometry(140, 160, 120, 32); QObject::connect(button, QPushButton::clicked, label, QLabel::clear); window.show(); return app.exec(); }上面的例子中点击 buttonlabel 的文本会被清空。connect 的第三个参数是接收者对象第四个参数是接收者成员函数也就是槽。这个写法的好处是如果 clicked 信号和 clear 槽的参数不匹配编译阶段就会报错不用运行到一半才暴露问题。有些老资料和旧代码使用宏写法connect(button, SIGNAL(clicked()), label, SLOT(clear()));这种写法运行时才解析字符串如果类名拼错、函数签名写错编译器不会及时提示。新项目建议使用函数指针写法。还有一类问题是信号存在重载。比如 QComboBox 的 currentIndexChanged 既有int版本也有QString版本。Qt 5 中连接重载信号需要帮助编译器区分connect(combo, qOverloadint(QComboBox::currentIndexChanged), this, [](int index) { qDebug() 当前索引 index; });如果不加qOverloadint直接写QComboBox::currentIndexChanged编译器可能因为无法确定重载版本而直接报错。这里也解释了一个常见问题为什么照着资料写 connect有时会编译失败。2. 先确认学习环境再搭一个最小控件工程2.1 Qt 版本与编译器要先对齐常用控件这一章有一个优势不需要数据库、网络、多线程这些额外依赖环境越简单越容易跑通。入门阶段使用的 Qt 版本会影响编译器选择和个别 API因此开始前要确认三件事Qt 版本、编译器工具链、工程构建方式。很多学习资料现在仍围绕 Qt 5.15.2 编写也有一部分已经切到 Qt 6。如果跟随一套基于 Qt 5 的课程或源码学习先不要急着装最新版最好先看课程开头说明使用的版本否则 QML、模块划分、某些枚举名可能会对不上。以 Qt Widgets 为例从 Qt 5 到 Qt 6 的改动相对较小但QRegExp、QTextCodec、部分枚举在 Qt 6 中已经被调整或移除查资料时要注意来源版本。下面是一份典型的学习环境参考| 项目 | 建议值 | 说明 | | --- | --- |