这次我们来看一套“重学 QT”体系里的第 02 章:常用控件(14 讲)。整个章节配套视频、源码和详细笔记,学习方式强调“从零一行行写代码”,不是打开 Qt Designer 拖两个控件就完事。这个定位值得单独拿出来讲:很多 Qt 开发者的真实状态不是不会用 QPushButton,而是对 QPushButton 背后的父子对象、布局策略、信号槽类型匹配和事件循环没有形成完整的知识闭环。控件是 Qt 桌面开发的入口,也是后面做串口、图表、多线程、网络通信这些实战项目时必须反复使用的一层。把这一章啃透,比急着去学 Model/View 或者 QML 更划算。
先说结论:如果你正在准备 Qt 面试、要维护老 QWidget 项目、或者用 Qt 做上位机但只能照抄 demo,这一章建议整章重过一遍;如果你是零基础且 C++ 语法还不熟,先把指针、引用、const、lambda 基础补一补再进来。文章会按照“先看环境能不能跑、再用最小代码验证控件、最后按清单排查问题”的顺序展开,尽量把 14 讲里最容易翻车的几个点说清楚。正文比较长,建议先收藏。
1. 常用控件章节核心信息速览
| 项目 | 说明 |
|---|---|
| 章节定位 | 重学 QT 基础篇第 02 章,承接第 01 章环境与基础,进入控件实战 |
| 课时体量 | 常用控件共 14 讲,每讲围绕一组 Qt Widgets 控件展开 |
| 课程资料 | 视频教程、配套源码、详细笔记 |
| 代码方式 | 从零一行行写代码,不是直接用 Designer 拖拽 |
| 主要功能 | QWidget 常用控件创建、属性设置、信号槽关联、布局组合 |
| 适合平台 | Windows、Linux 下 Qt Widgets 桌面应用 |
| 硬件要求 | 普通 CPU 电脑即可,最好能装 Qt Creator |
| 语法基础 | 需要 C++ 基础,至少会用类、指针、虚函数、lambda |
| 延续方向 | 信号槽机制、布局管理、样式表、Model/View、多线程 UI 更新、QCustomPlot/QChart 绘图 |
先说一个容易误判的点:14 讲不代表只有 14 个控件。Qt 基础控件数量很多,课程更常见的组织方式是把同族控件放在一起讲,比如 QLineEdit 和 QTextEdit 都是文本输入,QRadioButton、QCheckBox、QPushButton 都属于按钮族。学的时候要看懂基类和派生类的关系,而不是孤立地记每个类的 API。下面给出的学习路径和验证清单,也建议按这个思路去套,不必逐行背文档。
视频、源码、笔记三者怎么分配优先级?新手最容易犯的错是只看视频不动手。14 讲的节奏合适,哪怕每讲只有十几分钟,也建议每讲结束后关掉视频,对照笔记里的目标,从零写一遍核心代码。源码不是用来直接打开的,是用来做“最后对照”的。这样一套流程下来,代码量不多,但是每个控件都过了一遍真实使用场景,后面遇到问题也更容易定位是布局问题、信号槽问题还是控件生命周期问题。
2. 为什么要“重学”,而不是“初学”控件
很多 Qt 教程讲控件只讲一句“创建一个按钮,然后 connect 它的 clicked 信号”,但你在真实项目里会立刻碰到几个追问:这个按钮是 new 出来还是放在栈上?要不要传 parent?如果不传 parent,按钮关闭后会不会内存泄漏?按钮被 Layout 接管之后,布局对象和父窗口谁负责删除?这些问题在“用 Ctrl+C、Ctrl+V 写 Qt”的时候不会暴露,一旦自己从零构建一个窗口,马上就会卡住。
第 02 章强调从零一行行写代码,本质是在逼你建立自己的控件知识结构。一个控件从生到死至少要过这几步:构造时可以传 parent,但也可以在后续任意时刻 reparent;设置属性前要想清楚是字符串、数值还是枚举;addWidget 布局后控件尺寸由 layout 管理;控件要被 show 或者被父窗口带入显示状态;控件发出 clicked、textChanged、currentIndexChanged 等信号时,对应的槽函数要在正确的对象和线程里执行;窗口销毁时,子控件会被递归销毁,但是如果控件没有父对象,就必须自己保证释放。
Qt 控件的类层级也值得重新看一遍。QPushButton、QToolButton、QRadioButton、QCheckBox 都继承自 QAbstractButton,而 QAbstractButton 又继承 QWidget。只要理解了 QAbstractButton 提供了 setText、setIcon、click、setCheckable 这些公共能力和 clicked、pressed、released、toggled 这些信号,后面遇到任何“按钮类控件”都会自动联想过去。同理,QListWidget、QTableWidget、QTreeWidget 虽然形态差别很大,实际上都建立在 item widget 的模型之上。先掌握基类,再区分派生类差异,是效率最高的学习路径。
从面试角度说,控件也是 Qt 开发最容易被细问的地方。面试官很少问“QLabel 的构造函数会传入 parent 吗”,但如果聊你做过多少个窗口,大概率会问:怎么动态生成一组按钮、怎么在一个界面里嵌套多个 QTabWidget、表格里成百上千行数据为什么卡、信号槽断开怎么做、线程里能不能直接改 QLabel 文本。这些问题不会只考“认识某个控件”,而是考控件背后的对象模型和事件循环。第 02 章把控件本身的基础打牢,后面很多问题都能追回到这一章。
3. 适用场景与学习边界
不是所有人都需要以这一章为主路线。第 02 章适合四类读者:第一,C++ 基础还行,但 Qt 经验基本靠复制 demo,现在需要独立承担窗口模块;第二,做工业上位机或桌面工具的开发,界面主要是 QWidget/QDialog,需要把控件用得更规范;第三,准备系统补 Qt 基础的面试者,希望把常用控件、信号槽、源码结构串成完整知识线;第四,要带新人、写内部培训资料的技术负责人,可以参考 14 讲结构做练习清单。
如果不满足前提,建议先不要碰这章:完全不会 C++ 的读者,先补变量、类、继承、指针、STL 基础再回来看;目标是 QML/Qt Quick 移动端或嵌入式 HMI 的读者,Widgets 控件虽然能辅助理解概念,但主路线应转向 QML 类型系统;只想做一次性小工具、不考虑工程维护的读者,用 Designer 拖拽确实更快,这章的方法是帮你建立长期可维护能力,短期看“性价比”反而不高。
还要说明一个边界:第 02 章是控件使用,不包含完整的自定义控件绘制体系。如果要做炫酷皮肤、重绘仪表盘、地图轨迹、时域波形图,还需要后续的 QPainter、QPen/QBrush、事件重写和 QCustomPlot/QChart 专题。常用控件这一层解决的是“窗口搭得出来”,但一个窗口的视觉细节、动画流畅度、超大列表性能,属于图形绘制和 Model/View 优化问题。学完这章不要误以为自己已经把 Qt 图形全部掌握了。
合规边界的提醒也要放在前面。无论项目用视频里的源码还是自己写的控件逻辑,如果涉及真实业务系统,都要检查 Qt 开源版协议、源码依赖库的许可证,以及最终产品是否需要开源。Qt 按 LGPL 等协议使用时,通常需要允许用户替换 Qt 库,具体条款要按照实际版本和商用方式确认。此外,把控件用于工控或数据采集界面时,严禁直接连接未经授权的内网资源,凡是涉及他人文本、图片、字体、图标素材的,都要确保有授权。
4. 环境准备:把 Qt 跑通,再谈控件
这章说的“环境”不复杂,但对新手来说一个明显问题是:看视频时讲师用的版本和本地不一致,导致 API 明明一样,编译却各种报错。Qt 5 和 Qt 6 都支持 QWidget 常用控件,常用控件 API 多数兼容,但 CMake 配置方式、高 DPI 默认行为、部分头文件位置有差异。视频配套源码基于哪个版本,最好先看笔记里的要求。没有明确说明时,我建议 Qt 5.15.2 或 Qt 6.5 之后的 LTS 版本都可以,二选一后尽量保持一致。
安装 Qt 工具链时要关注的不是“默认下一步”,而是组件勾选。使用 Qt Widgets 应用,至少需要当前平台的编译器支持:Windows 常用 MSVC 或 MinGW,Linux 用 GCC。安装 Qt 时把对应编译器套件、Qt Debugger、Qt Creator、Sources 勾上,宁可磁盘多占几 GB,也不要等到编译时才发现缺少 Symbols 或调试器。初学者最怕的报错“could not find the qt platform plugin windows”“qt.qpa.plugin: could not load the Qt platform plugin”通常不是控件代码问题,而是环境没有配对。
先做一个不依赖任何自定义控件的空白窗口。新建项目时选 Qt Widgets Application,也可以手动建立目录和源文件。最简单的 main.cpp:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("控件章节环境验证"); label.resize(320, 120); label.show(); return app.exec(); }.pro 工程文件可以很简单:
QT += widgets CONFIG += c++17 TARGET = env_check TEMPLATE = app SOURCES += main.cpp如果使用 CMake,Qt 6 的基础 CMakeLists.txt 可以这样写:
cmake_minimum_required(VERSION 3.16) project(env_check LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_add_executable(env_check main.cpp) target_link_libraries(env_check PRIVATE Qt6::Widgets)编译成功并弹出窗口,说明 qmake/CMake、编译器、Qt 库、插件链已经通了。如果运行后没有窗口而是报错,可以先检查两个方向:构建时用的 Kit 是否和安装的 Qt 编译器一致;程序运行时是否能找到 Qt 的 plugins 目录。开发环境下,Qt 的 bin 和 plugins 路径通常已经写入安装器;如果手动拷贝 exe 到别的目录运行,就要用 windeployqt 把 Qt 运行库和 platforms 目录带过去。
5. 从零一行行写代码:每讲最好这样拆解
第 02 章有 14 讲,如果用屏幕看视频,会看到讲师在代码编辑器里从空文件开始敲。很多人误以为这种演示只是“为了显得认真”,实际上这是一个很高效的拆解法:不要一次写整个窗口,也不要直接在 Qt Creator 里用双击生成 UI 文件。正确方式是把每一讲的大目标拆成小步,每步能看到编译和运行结果。
以按钮和文本输入为例,可以把一个简单联动拆成几步:第一步,创建 QWidget 作为主窗口;第二步,放一个 QPushButton、一个 QLineEdit、一个 QLabel;第三步,设置水平和垂直布局,明确控件在窗口缩放时的伸缩行为;第四步,连接按钮的 clicked 信号,让 QLabel 显示 QLineEdit 当前文本;第五步,编译运行,验证点击效果;第六步,再增加边界情况,比如输入为空时提示或者禁用按钮。每一步都是可以运行的,而不是写完一个几百行的文件再去找错误。
为什么强调一行行写代码而不是直接使用 Designer?因为 Designer 生成的 .ui 最后也会编译成代码,但控件之间的 parent、objectName、layout 关系被可视化了,你容易失去对“代码如何组织”的敏感。运行期动态创建控件时没有 .ui 帮你管理,父子关系、信号槽、布局层级都必须写清楚。先练习纯代码,之后再学 .ui 才能真正理解 UI 文件是怎么工作的。反过来,如果你已经熟练 Designer,也建议在工程里查看 ui_xxx.h 里生成的 setupUi 函数,这比任何教程都更能还原控件代码结构。
再给一个纯代码的练习模板。目标是用 QSlider 控制 QLabel 显示数字,看控件之间的数值联动:
#include <QApplication> #include <QWidget> #include <QSlider> #include <QLabel> #include <QVBoxLayout> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle("Slider 到 Label 联动"); QSlider *slider = new QSlider(Qt::Horizontal); slider->setRange(0, 100); slider->setValue(30); QLabel *label = new QLabel("30"); label->setAlignment(Qt::AlignCenter); // QSlider::valueChanged 不存在重载,写法最稳 QObject::connect(slider, &QSlider::valueChanged, label, [label](int value) { label->setText(QString::number(value)); }); QVBoxLayout *layout = new QVBoxLayout(&window); layout->addWidget(slider); layout->addWidget(label); window.show(); return app.exec(); }这段代码正确连接到 label,但只有当你确认了 QSlider 的 valueChanged 参数是 int 之后才敢这么写。如果换成 QSpinBox,它的 valueChanged 信号在 Qt 中有重载,直接用 &QSpinBox::valueChanged 作为函数指针会出现歧义。课程在讲“一行行写代码”时,这类细节就是最容易暴露基础是否牢固的地方。
配套源码到手之后,不要先急着全看。每讲源码可能有多个文件,先只看 main.cpp 或单个 widget 的实现,看懂它创建了哪几个控件、每个控件构造函数里的 parent 是谁、哪些控件被 addWidget 了、connect 的发送者和接收者是谁。如果能在不看源码的情况下自己复现同一逻辑,再去对照源码,差异点就是这讲最值得重点理解的坑。
6. 常用控件的学习地图与验证清单
第 02 章 14 讲如何分配没有统一答案,但常用控件通常会被划分为显示类、按钮类、文本输入类、数值输入类、列表表格树类、容器和窗口结构类。每一类抓的核心点不一样,学习时用的验证方法也不一样。
显示类控件中,QLabel 最典型,常用属性是 text、alignment、wordWrap,还有 pixmap 显示图片,信号比较少,主要靠外部代码改变它的内容。学习 QLabel 的时候要重点验证“文本变化后布局如何更新”“长文本不换行会怎样”“图片和缩放模式怎么配合”。QProgressBar 可以归入显示与反馈,它一般依赖外部代码更新 value,适合在后续多线程任务里使用。
按钮类控件以 QPushButton、QRadioButton、QCheckBox、QToolButton 为代表。它们都能通过 setText、setIcon 设置界面,setEnabled 控制可用性。QPushButton 最常用 clicked,QRadioButton 和 QCheckBox 则适合用到 toggled 信号。做验证的时候,可以把三个按钮放到 QButtonGroup 里,观察单选和互斥行为能不能符合预期。
文本输入类控件要分开对待。QLineEdit 是单行输入,学习重点包括 placeholderText、setEchoMode,用它做密码框、搜索框很常见;QTextEdit 是多行富文本编辑器,它可以展示纯文本、HTML 或 Markdown,实践项目里也大量用来显示串口日志、聊天记录、文本日志。这里最容易出问题的是“输入事件触发频率”。在 QLineEdit 的 textChanged 信号里做每次按键处理,槽函数会很频繁执行,所以要掌握什么时候用 editingFinished,什么时候用 textChanged。
数值输入类控件中,QSpinBox、QDoubleSpinBox、QSlider、QDateTimeEdit 都很常用。QSpinBox 与 QSlider 经常做联动,用来设置电机速度、音量、阈值等参数。QDateTimeEdit 在项目中常与 QCalendarWidget 配合,用来做日期区间选择。学到这里,可以尝试做一个“参数设置区”:用 QSpinBox 设置数量,用 QSlider 快速拖拽,同时用一个 QLabel 实时显示最终值,这比单独练习一个控件更有用。
列表表格树类控件是很多工程软件的界面核心。QListWidget 适合简单列表,QTreeWidget 适合树形结构,QTableWidget 适合行列表格。它们的快速 demo 都不难:addItem、setItem、setCellWidget、currentRow 这些基础 API 很容易上手。但工程里会出现大量行的列表,这时只靠 item widget 会卡顿,之后需要了解 Model/View 和自定义委托。第 02 章如果只要求“能用”,你至少要知道 QTableWidget 的行列、表头和 item 选择怎么取;如果要处理几十万行,那不能停留在 item widget,要往 QTableView 和 QAbstractTableModel 方向走。
容器类和窗口结构类包括 QGroupBox、QTabWidget、QSplitter、QScrollArea、QMenuBar/QToolBar/QStatusBar。它们的核心作用是组织界面。验证时重点看两件事:加入子控件后布局是否替换、窗口拉伸时容器内部的控件到底按什么策略变化。QGroupBox 可以给一组单选按钮提供分组边界,QTabWidget 用来切页,QScrollArea 可以包住超出可视区域的内容。只看单个控件外观不够,必须拖到整体布局里去验证。
每讲学完不要靠感觉判断“会了”,可以用下面的问题自查:
| 控件 | 你会不会写这些? |
|---|---|
| QPushButton/QRadioButton/QCheckBox | setText、setEnabled、clicked、toggled、QButtonGroup 分组 |
| QLineEdit/QTextEdit | text、placeholderText、echoMode、textChanged、多行日志显示 |
| QComboBox | addItem、currentIndexChanged、setEditable、获取当前文本 |
| QSpinBox/QSlider | setRange、setValue、valueChanged、数值联动 |
| QListWidget/QTableWidget/QTreeWidget | 增删条目、读取当前选中项、表头设置 |
| QTabWidget/QGroupBox/QSplitter | 动态加页、嵌套布局、拉伸比例 |
| QProgressBar/QLabel | 进度更新、文本/图片显示、样式表改变外观 |
更直接的办法是反过来做题:给一个需求,不给控件名,让你选择用什么控件。要求“下拉选一个方案,右侧显示对应说明文字”,你要想到 QComboBox + QStackedWidget;要求“左侧分类树、右上详细表格、右下日志”,你要想到 QTreeWidget + QTableWidget + QTextEdit 加 QSplitter。这种“需求到控件”的映射能力,才是 14 讲练完之后留下来的能力。
7. 信号槽是控件练习里最绕的一环
把常用控件练得差不多后,最大的难点就集中在信号槽上。控件本身只是“带界面的对象”,真正让界面动起来的是信号槽。第 02 章虽然主题是控件,但每讲都脱离不了 connect,所以要提前把信号槽的基础用法摸清。
新版 Qt 推荐使用函数指针语法,因为它在编译期就能检查信号和槽是否存在,类型不匹配会直接报错。比如连接按钮点击和窗口关闭:
connect(button, &QPushButton::clicked, this, &QWidget::close);如果槽函数带参数,信号参数必须能转换为槽参数。clicked 信号还有一个 bool checked 参数,如果你使用QPushButton::clicked连接到无参 lambda,编译通常没问题;但如果你的 lambda 想接收 checked 状态,就要写成[this](bool checked){ ... }。这些细节用旧版字符串形式的 SIGNAL/SLOT 宏可能到运行期才暴露,新语法则更早发现问题。
lambda 是控件练习里最灵活的连接方式。不过 lambda 默认不会延长 receiver 生命周期,只负责执行逻辑。常见错误是在 lambda 里捕获了 this 或控件指针,结果界面已经销毁,线程回调却还在跑。处理办法是判断生命周期,或者让发送者和接收者都挂在同一父对象上,关闭窗口时整个对象树被释放。另一个小坑是 lambda 捕获了局部 QWidget 指针,但如果该控件没有父对象,它生命周期独立于窗口,窗口关闭后它仍存活,Lambda 执行时可能操作一个隐藏但不销毁的控件,逻辑可能出乎意料。
带重载的信号也是新手高频报错点。QComboBox 的 currentIndexChanged 有 int 和 QString 两个重载,QSpinBox 的 valueChanged 在 Qt 中也有 int 和 QString 两种形式。直接用 &QSpinBox::valueChanged 作为参数会得到“no matching member function for call to connect”之类的错误。解决方法是显式指定类型:
connect(spin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) { label->setText(QString::number(value)); });Qt 6 中还提供了 qOverload 模板,本质上也是在编译期选择正确的信号重载版本。学控件时遇到这种错误不要跳过,它是在帮你理解 C++ 的函数指针类型和信号重载。
信号槽还可以跨线程使用。第 02 章不要求你写多线程,但要记住一个边界:在工作线程里不能直接调用控件对象的 setText、show、hide 等方法,因为 QWidget 只能在主线程操作。正确做法是把要传出的数据封装成信号发出来,由主线程的槽函数更新 UI。如果未来在老项目里看到界面卡死、崩溃、偶发不刷新,好多都和“线程里操作了控件”有关。这也是为什么基础篇后面会有事件循环和线程章节,它们和常用控件是配套的。
8. 生命周期、父子对象与控件释放
写控件代码时,“用指针 new 出来的控件到底由谁 delete”必须有一个统一答案。Qt 的父子对象机制约定:给控件指定 parent,父析构时会递归 delete 所有 child;但如果你先 setParent 到一个窗口,后来又把该控件 reparent 到另一个窗口,所有权也会跟着变化,旧的父对象不再替它释放。
回忆一下常见代码。下面的写法把 label 作为 child 传给 window,window 生命周期结束时 label 自动释放:
QWidget *window = new QWidget; QLabel *label = new QLabel("我是子控件", window);这种写法简洁,但如果不理解所有关系,有人可能会在中途手动 delete label,导致 window 析构时发生二次释放。另一个常见误区是把控件加入布局后再手动 delete。layout->addWidget 不是转移普通智能指针,addWidget 在绝大多数情况下会把控件 reparent 到 layout 所在的 widget,所以在布局容器销毁后再手动 delete 子控件,同样会触发重复释放。
内存管理的稳妥策略是:所有 QWidget 对象尽量指定一个明确的 parent,不必手工 delete。顶层窗口或需要延迟释放的对象使用 QObject::deleteLater。如果某个控件确实需要独立存在,用 std::unique_ptr 或 QPointer 去管理时要格外小心。做练习时想要内存不炸,最简单的规则是“从一个普通变量开始,逐步 setParent 或 addWidget,不要到处散落裸 new 后的孤儿控件”。
生命周期还影响信号槽。如果 receiver 对象先于 sender 被销毁,Qt 的信号槽连接默认会自动断开,不会立刻崩溃,这也是 Qt 对象树设计带来的好处。但如果接收者是 lambda 捕获的裸指针,而该裸指针不是 QObject,Qt 无法感知它什么时候失效。因此建议在 lambda 中尽量不捕获已经失控的控件裸指针,或者在稍大逻辑里把捕获范围控制为 this、索引、值类型,等回到主线程再用安全的 findChild 或成员指针操作。
9. 配套源码和笔记:拿到手后怎么用
第 02 章的配套源码和详细笔记,是这门课比其他纯视频内容更高效的部分,但前提是你会用。一拿到源码不要直接全部拽进工程里编译,先按目录结构认识文件名,判断每个示例对应哪一讲。Qt 工程目录往往包含 .pro 或 CMakeLists.txt、main.cpp、widget 类文件,有些示例还带 .ui。先看看主函数里都创建了什么控件,再进入类实现里看构造函数的 setup 过程。
笔记的推荐用法是“转译”成自己的表格。每学一个控件,手写或维护一张卡片,内容包含:控件类名、头文件、典型构造函数参数、常用 setter/getter、发射的核心信号、需要注意的重载或父类特性。不要让笔记停留摘抄。例如 QListWidget 卡片上如果写了“常用信号 currentRowChanged”,你就要配套写下“当前行变化时,我可以结合 QStackedWidget 切页”,这才是可迁移的知识。
源码和视频的差集正是你的学习收获。先写,跑不动再打开源码查;先看视频演示,再关掉视频把代码补全。如果源码能直接编译,尝试刻意改造:把单选按钮改成复选框,把 QSpinBox 改成 QDoubleSpinBox,把列表项点击改成一键删除。每处改动都可能引入新编译错误,这些错误比视频里见过的正常执行过程更值钱。改错了不要立刻回滚,先把报错信息读完整,养成看“编译器前几行关键信息”和“构建输出尾部错误”的习惯。
10. 常见问题与排查方法
控件学习阶段的报错集中在几类:环境插件错误、编译链接错误、运行期界面不显示、信号槽无响应、中文乱码。下面把最常碰到的现象和排查路径整理成表:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动提示 could not find the Qt platform plugin "windows" | 系统找不到 Qt 的 platforms 插件 | 看可执行文件旁有没有 platforms 目录,检查 PATH 是否有 Qt bin | 开发环境检查 Kit 是否正确;发布目录用 windeployqt 补齐插件 |
| Linux 下提示 qxcbconnection: failed to initialize xrandr 或 xcb 加载失败 | Qt xcb 平台插件或依赖库缺失 | 启动带环境变量 QT_DEBUG_PLUGINS=1,查看缺失库 | 安装 xcb、xkbcommon、xrandr 依赖,Qt6 常需要 libxcb-cursor0 |
| 窗口没有显示,程序运行后很快退出 | 忘记调用 show,main 中没有执行 app.exec | 在构造函数或 main 里检查 show | 所有顶层控件要 show,main 最后 return app.exec() |
| 点击按钮后槽函数不触发 | connect 写错、sender/receiver 生命周期问题、信号被覆盖 | 在槽函数第一行加 qDebug;检查 connect 是否执行 | 使用新式 connect,确认发送者对象是实际点击的按钮 |
| 控件中文显示乱码 | Qt 5 MSVC 下源文件编码与编译器读取编码不一致 | 检查源文件编码 | Qt 6 默认 UTF-8;Qt5 MSVC 加 /utf-8 或统一 UTF-8 with BOM |
| 编译报错 undefined reference to vtable | 类中有 Q_OBJECT 但 moc 未生成 | 检查是否启用 AUTOMOC 或 .pro 里 HEADERS 已包含头文件 | 清理 build 目录重建,检查 CMake AUTOMOC |
| 布局后控件大小不随窗口变化 | 没设 Layout 拉伸比例或 sizePolicy | 拖拽窗口观察控件边界 | 设置 QSizePolicy、layout->setStretch,检查最小尺寸 |
| QSpinBox/QComboBox 信号用函数指针 connect 报类型不匹配 | 信号有重载 | 查看函数提示,确定具体参数 | 用 QOverload ::of 或 qOverload |
| 关闭窗口后后台进程不退出 | 事件循环被其他顶层窗口或定时器占用 | 观察关闭行为 | 确认所有顶层窗口关闭、QApplication::quitOnLastWindowClosed 默认行为 |
在这些问题里,“ could not find the Qt platform plugin ”虽然是环境不是控件,但它恰恰会在你最想看到控件时出现。开发环境跑 Qt 工程,Qt 库和 plugin 的路径由编译器环境变量或安装器写入;如果你手动把 debug 目录里生成的 exe 拷到其他地方运行,exe 旁边没有 platforms 目录,Qt 就会报这个错。解决方案不是到代码里改,而是把 Qt 对应的插件目录一并复制。Windows 下使用windeployqt最可靠,它会复制 Qt 运行库、插件和编译器运行库。
Linux 下出现 qxcb 相关报错时,执行一下项目的二进制往往能给出更具体的 xcb 库缺失。Qt 安装时自带的平台插件依赖系统图形库,和系统自带的 Qt 库冲突也可能导致插件版本不一致。常用做法是设置QT_DEBUG_PLUGINS=1输出插件细节,方便快速定位。和控件代码关系不大,但不解决它,后面所有控件页面都无法弹出。
如果点击按钮没有反应,先用最简单的方法判断:在槽里加qDebug() << "triggered";。如果点击时没有任何输出,说明 connect 没生效或者发送者不对;如果输出了但界面没变,问题在槽实现逻辑或控件引用到了别处。信号槽连不上的一种隐蔽原因是运行时又给同一个控件执行 setObjectName 或换了代理,导致事件被重新路由。遇到这类问题不要反复改 connect,而是确认连接的是同一个控件实例。
中文乱码问题在使用 MinGW 编译器时较少,因为很多环境默认按 UTF-8 处理;使用 MSVC 编译器时,若源文件是 UTF-8 无 BOM 而 MSVC 默认按 GBK 读取,字符串常量就会乱码。最稳妥的做法是:Qt 5.15 MSVC 项目在 .pro 中增加msvc: QMAKE_CXXFLAGS += /utf-8;CMake 项目可以给 target 加上/utf-8编译选项,或统一把源文件存成带 BOM 的 UTF-8。Qt 6 中情况改善很多,但老代码迁移时仍要关注。
编译报错出现 “undefined reference to vtable for” 时,几乎都和 Q_OBJECT、moc 有关。Qt 的元对象系统需要在预处理阶段扫描类声明里的 Q_OBJECT 并生成 moc 文件。qmake 工程要把含有 Q_OBJECT 的类头文件加入 HEADERS;CMake 要开启 AUTOMOC 或使用 qt_add_executable 提供的自动 moc。如果代码从旧文件夹拷贝过来没重新构建,清理掉 build 目录后重新 qmake/cmake 往往就能恢复。
11. 学完控件后,向实战项目延伸的路
第 02 章结束不代表控件就此学完。真正会写控件的标志不是能写出十几个单独窗口,而是能把一组控件组合成一个可用工具。如果你是冲着“重学 QT”的完整路线来的,接下来可以试着做一个综合小项目,把前三章知识串起来。
举个例子,很多热词里提到的“qt 串口编程”“qt 时域图转换为频域图,使用 qcustomplot 显示”都是典型控件应用。做一个串口调试助手,界面层至少要用到 QComboBox 枚举可用串口、QTextEdit 显示收发的日志、QLineEdit 输入发送内容、QPushButton 控制打开和发送、QTimer 做轮询或超时控制。这些控件单独拉出来很简单,但要配合 QSerialPort 的 readyRead 信号、QByteArray 数据转换、打开失败提示、接收缓冲区长度控制,难度立刻就上来了。
做一个信号频谱显示工具也一样,底层是 FFT 计算,界面层用 QCustomPlot 或 QChart 画曲线。要在界面上选择信号源、设置采样点数、点击开始采集、动态刷新曲线,你仍然要依赖 QComboBox、QSpinBox、QPushButton、QTimer、布局以及信号槽协作。第 02 章的 14 讲如果没消化,后面加再漂亮的绘图库也会感觉界面架构很乱。
后续章节大概率会往更底层和更抽象的方向走。QPainter 自定义绘图、QEvent 事件处理、Model/View 架构、视图委托、线程与事件循环,都会反过来影响你现在写控件的方式。学习建议是不要把章与章切得太开,可以每学完一个新知识点,就回到常用控件章节做一个小改造:控件加一个自定义绘制按钮,列表换成 QTableView + model,日志显示用多线程接收。这个循环越滚越大,你对 Qt 的理解才会从“能运行”变成“能设计”。
第 02 章真正的收获,不只是看懂了几个控件的属性窗口,而是建立了一套从对象创建、布局到信号关联的稳固定式。后面开会话框、主窗口、复杂业务界面时,都可以沿用它。建议把视频、源码、笔记按“先动手再对照、对照后改造、改造后记录”的节奏排进学习计划,整章过完后做一个小工具收尾,再进第 03 章。那样你会发现后面的内容顺很多。