简介:《C++ Qt5 范例开发大全》中文版以 PDF 电子文档形式提供,面向具备 C++ 基础、希望系统掌握 Qt5 桌面应用开发的初、中级程序员,也适合作为随手翻检的查阅型工具书。全书以范例驱动,从 Qt Creator 的下载安装与第一个 Hello World 程序起步,依次展开窗体基类以及窗口大小、初始位置、背景色、无边框拖动、多窗体调用等界面控制技巧,讲解 QPushButton、QLineEdit、QComboBox、QTreeView、QTableView 等常用控件与横向、网格两种布局的排布思路,并延伸至图形图像绘制与加解密应用等进阶主题。压缩包共 1 个文件,为 2.83MB 的 PDF,目录层级清晰,可按章节直接定位范例与代码片段。目前已有 256 人学习,适合边看边敲、对照实践,用来补齐 Qt5 开发中的关键知识点。
1. 从范例目录到可交付程序:Qt5 桌面项目要先立住的三件事
拿到一份 C++ Qt5 范例开发大全,最常见的用法是照着目录从开发环境一路敲到打包部署,十四章敲完,关掉 Qt Creator 却还是拼不出一个能交的窗体程序。问题不在例子太少,而在于没有先立住三件事:工程骨架怎么搭、界面用什么控件和布局拼、数据往哪儿落。这套 Qt5 范例开发的价值,是把窗体、控件、文件、数据库、图形图像、网络、线程、加密一直到打包部署串成一条能走通的链路,每一段都有可运行的源码对照。它适合两类人:要交毕设或课程设计、需要一个完整 Windows 桌面程序的学生;以及接了内部小工具、得在两三周内把原型做出来的开发者。下面按「先搭骨架、再拼界面、然后通数据、最后验收」的顺序拆开。
2. 开发环境与工程骨架:Qt Creator、三种基类与资源文件的正确开局
2.1 工具链落位:Qt Creator 与 MinGW 套件的取舍
Qt 开发环境的坑,八成出在"装完了但跑不起来"。范例里给的组合是 Qt Creator 加一个自带 MinGW 编译器的离线安装包,Windows 32 位、OpenGL 支持。这个组合的好处是不依赖本机已装的 Visual Studio 运行时,装完就能编译,对课程设计这种"换台电脑就得重新配"的场景最省事。安装过程中唯一需要手动做的一步,是把编译器的 bin 目录写进系统 PATH,很多人跳过这步,结果编译能过,双击 exe 却弹窗报缺库。
# 把 Qt 的 bin 目录加进 PATH,运行时才找得到 Qt5Core.dll 等动态库 # 注意分号分隔,路径按自己的安装位置替换 set PATH=D:\Qt\Qt5.1.1\5.1.1\mingw48_32\bin;%PATH% # 验证是否生效:能打印版本号说明 PATH 配对了 qmake -v这段命令做两件事:一是把动态库搜索路径前移,二是让 qmake 在任意目录下都能被调用。PATH 里的分号容易被漏掉,漏掉的结果是把两条路径粘成一条,系统认不出来也不报错,只是不生效,属于典型的玄学问题。装完之后先在命令行跑一次qmake -v,能出版本号,再去建项目,顺序别反。
提示:环境变量改完要重开 Qt Creator,已经打开的进程读的是旧的 PATH。
2.2 三种基类选型:QMainWindow、QWidget、QDialog 各管一摊
新建项目时那个选择框,是整本书里最早也最容易被随手点过的地方。三种基类的差别不是风格问题,是自带部件的差别,选错了后面全是补丁。
| 基类 | 自带部件 | 适合的场景 | 选错之后的代价 |
|---|---|---|---|
| QMainWindow | 菜单栏、工具栏、状态栏、中央部件区 | 主界面、多文档工具 | 用 QWidget 想加状态栏,得自己拼布局,还得管停靠 |
| QWidget | 无,纯空白画布 | 自定义控件、单页面板 | 当主窗口用会缺菜单栏,后期补起来很别扭 |
| QDialog | 默认按钮区、返回值、模态控制 | 登录、设置、确认框 | 当主窗口用,模态语义和返回值都用不上 |
我一般的判断顺序是:这个界面有没有"文件/编辑/帮助"这类全局菜单,有就 QMainWindow;是不是弹出来让用户确认一件事、点完就关,是就 QDialog;剩下的一律 QWidget。一个稍微完整点的桌面程序,通常是 QMainWindow 当壳,中央部件里塞 QTabWidget,各个页签内容用 QWidget 封装,登录和设置用 QDialog 弹。这个组合在范例的窗体章和组件章里反复出现,照着搭一遍就能体会出分工。
2.3 从 Hello World 读懂 main.cpp 与 .pro
第一个例子看起来简单,但它把 Qt 程序的运行模型讲全了。入口函数里创建 QApplication,创建主窗口,show 出来,最后进 exec 的事件循环,四步缺一不可。
// main.cpp:所有 Qt GUI 程序都长这样,区别只在中间那一行 #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { // argc/argv 是命令行参数,Qt 自己会解析一部分(比如 -style) QApplication a(argc, argv); MainWindow w; w.show(); // show() 只把窗口标记为可见,不会卡住 return a.exec(); // exec() 才是事件循环,程序在这里等鼠标键盘 }a.exec()这一行是新手最容易误解的:以为程序到 show 就结束了,其实 show 之后立刻返回,真正让窗口"活着"、能响应事件的是 exec 里的循环。所以任何放在 exec 之后的代码都不会执行,需要延后做的事,要么用 QTimer::singleShot,要么挂到信号槽上。另一个细节是 argc/argv 要原样传给 QApplication,不能传空,否则带参数的启动方式会失效。
工程文件 .pro 同样值得看一眼,它是 qmake 的构建描述,而不是随手生成的杂物。
QT += core gui # Qt5 把 widgets 拆成了独立模块,这行负责兼容 Qt4/Qt5 两种写法 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QtOne # 生成的 exe 名字 TEMPLATE = app # 应用类型,库项目要改成 lib SOURCES += main.cpp mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.uigreaterThan那行的写法是老项目传下来的习惯,作用是让同一份 .pro 在 Qt4 和 Qt5 下都能编。现在只用 Qt5 可以省掉,但接手别人的工程时见到它别删,删了可能整套构建就散。另外 debug 和 release 两个目录是构建时自动生成的,源码目录里看不到它们,说明还没编译过;这两个目录里的东西不要往版本库里提交。
2.4 资源文件 qrc:图标和图片为什么显示不出来
改标题栏图标那节,实际考的是资源系统。图片直接放项目目录,用相对路径也能读到,但一旦打包换机器就找不到,正确做法是走 qrc 编译进二进制。步骤是建一个资源文件、加前缀、加文件、起别名,然后在代码里用冒号加路径引用。
<!-- image.qrc:前缀是逻辑目录,alias 是不带后缀的短名字 --> <RCC> <qresource prefix="/new/prefix1"> <file alias="ico">resource/QT.ico</file> </qresource> </RCC>// 路径规则是 : + 前缀 + / + 别名,别把后缀写进 alias this->setWindowIcon(QIcon(":/new/prefix1/ico"));这里翻车的姿势非常统一:别名起了ico,代码里却写:/new/prefix1/ico.ico,或者前缀写成了/,编译期不报错,运行时图标就是空白。排查办法是编译后在 Qt Creator 的资源编辑器里看树形结构,把鼠标悬停在文件上,它会把完整路径显示出来,照抄就行。图标本身建议用 16×16 或 32×32 的 ICO,PNG 也能用,但任务栏显示会略糊,属于观感问题不是功能问题。
3. 界面层搭建:窗体定制、控件选型与布局管理的组合套路
3.1 窗体外观四件事:尺寸、位置、背景与标题栏
窗体章的例子拆得很碎,但归拢起来就是四件事:多大、在哪儿、什么底色、标题栏长什么样。这四件事各自对应一组 API,写在一起就是一段模板代码,后面每个新窗口都能直接抄。
// 固定大小 300x300,位置 (100,100),红底,自定义图标 this->setWindowTitle("窗体应用示例"); this->setMaximumSize(300, 300); this->setMinimumSize(300, 300); // 最大最小设成一样,窗口就拖不动了 this->move(100, 100); // 相对屏幕左上角的坐标 this->setStyleSheet("background:red"); this->setWindowIcon(QIcon(":/new/prefix1/ico")); // 只保留关闭按钮,去掉最大化和最小化 this->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint);setMaximumSize和setMinimumSize设成同值是固定尺寸的常用写法,比setFixedSize更直白,效果一样。setWindowFlags有个顺序坑:它会让窗口重建,如果在show()之后调用,窗口会先闪一下再以新样式出现,个别系统上甚至直接不显示。正确位置是在构造函数里、show 之前调用。背景色用样式表是最省事的,但要注意它会影响子控件继承,复杂界面里更稳的做法是重写paintEvent或者给样式表加选择器限定范围。
3.2 无边框窗体的拖动:三个鼠标事件的正确写法
无边框窗体是"看起来简单、写起来必错"的典型。去掉标题栏之后窗口就没法拖了,得自己接管鼠标事件。核心逻辑只有一句:按下时记录鼠标全局坐标与窗口左上角的差值,移动时用当前全局坐标减去这个差值。
// mainwindow.h protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: QPoint m_offset; // 按下点相对窗口左上角的偏移 bool m_dragging = false; // 标记是否处于拖动状态// mainwindow.cpp void MainWindow::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_dragging = true; // 用 globalPos 与 frameGeometry 求差,窗口才不会跳动 m_offset = e->globalPos() - frameGeometry().topLeft(); } QWidget::mousePressEvent(e); // 交还给基类,别吞掉事件 } void MainWindow::mouseMoveEvent(QMouseEvent *e) { if (m_dragging && (e->buttons() & Qt::LeftButton)) { move(e->globalPos() - m_offset); } QWidget::mouseMoveEvent(e); } void MainWindow::mouseReleaseEvent(QMouseEvent *e) { m_dragging = false; QWidget::mouseReleaseEvent(e); }这段代码有两个关键参数:globalPos()是相对屏幕的坐标,frameGeometry()是含边框的窗口矩形。用pos()代替globalPos()的话,鼠标移动过程中窗口位置一变,参照系跟着变,窗口会以越来越大的幅度抖动,这是最经典的翻车现场。另外m_dragging这个标记不能省,只靠e->buttons()判断的话,在窗口外部释放鼠标后状态会残留,下次按下前窗口就开始乱跑。
3.3 控件选型对照:二十多个常用控件怎么挑
控件章列了二十多个类,逐个背没意义,按"输入什么、展示什么"分组记更有效。下面这张表是按实际使用频率挑出来的,覆盖了绝大多数课程设计的需求。
| 控件 | 负责什么 | 关键信号 | 容易踩的点 |
|---|---|---|---|
| QPushButton | 触发动作 | clicked() | 连接写错重载版本,编译过但点了没反应 |
| QLabel | 展示文字/图片 | linkActivated() | 想换行要 setWordWrap(true) |
| QLineEdit | 单行输入 | textChanged() | 密码框要 setEchoMode(Password) |
| QTextEdit | 富文本多行 | textChanged() | 取纯文本用 toPlainText() |
| QPlainTextEdit | 大段纯文本 | textChanged() | 日志场景比 QTextEdit 省内存 |
| QComboBox | 下拉选择 | currentIndexChanged() | 索引和文本别混用 |
| QFontComboBox | 字体选择 | currentFontChanged() | 默认只列系统已装字体 |
| QSpinBox | 整数微调 | valueChanged() | 范围要设,默认 0~99 很憋屈 |
| QDateEdit / QTimeEdit | 日期时间 | dateChanged() | 显示格式跟着系统区域走 |
| QRadioButton | 多选一 | toggled() | 同组要放进同一个 QButtonGroup |
| QCheckBox | 多选多 | stateChanged() | 三态模式要单独开 |
| QListView / QTreeView / QTableView | 数据展示 | 配合模型类 | 直接塞数据不如用模型视图 |
选型上有个简单原则:能用现成控件就别自绘。QTableView 配 QSqlTableModel 能把数据库表直接显示出来,比自己拼一堆 QLabel 省一整天。单选按钮那一行的坑最多,同一父容器里的 QRadioButton 默认自动互斥,如果你把两组单选放进同一个 GroupBox,它们会跨组互斥,表现就是"选了这个,另一个组里的也弹起来",解决办法是给每组套一个独立的 QGroupBox 或 QButtonGroup。
3.4 布局管理:QHBoxLayout 与 QGridLayout 的配合
布局是新手的第二个分水岭。用绝对坐标move()摆控件,自己的电脑上好好的,换台分辨率不同的机器就全挤在一起。布局管理器的价值是把"位置"变成"关系",窗口拉伸时控件按规则重新分配空间。
// 登录区:两行输入 + 一行按钮,第二列吃掉多余宽度 QGridLayout *grid = new QGridLayout; grid->addWidget(new QLabel("用户名"), 0, 0); grid->addWidget(new QLineEdit, 0, 1); grid->addWidget(new QLabel("密码"), 1, 0); grid->addWidget(new QLineEdit, 1, 1); QHBoxLayout *btns = new QHBoxLayout; btns->addStretch(1); // 弹簧把按钮推到右边 btns->addWidget(new QPushButton("登录")); btns->addWidget(new QPushButton("取消")); grid->addLayout(btns, 2, 0, 1, 2); // 跨两列,占一行 grid->setColumnStretch(1, 1); // 第 1 列可伸缩 grid->setRowStretch(3, 1); // 底部留白吸收多余高度 QWidget *central = new QWidget; central->setLayout(grid); setCentralWidget(central);addWidget的后四个参数是行、列、跨行数、跨列数,addLayout同理但接受一个布局对象。setColumnStretch决定多余空间怎么分,数值是相对权重,不设的话所有列平分,输入框会被拉得很宽很难看。addStretch是插入弹簧,用在需要把某几个控件推到边缘的场合。几个原则记一下:外层用 QVBoxLayout 分行,每行内部用 QHBoxLayout,需要对齐的表格状区域用 QGridLayout,嵌套两层基本能解决九成界面。
3.5 组件与主框架:Tab、分组框、菜单工具栏与托盘
控件解决"单个元素",组件解决"一整块功能"。范例里那一章列的东西看起来杂:日历、登录窗口、文件浏览对话框、颜色选择、进度条、定时器。它们共同点是都由多个控件拼出来、都有固定的调用套路。
// 多页签 + 托盘图标,是"看起来像个正经软件"的最低配置 QTabWidget *tabs = new QTabWidget(this); tabs->addTab(new QWidget, "基础设置"); tabs->addTab(new QWidget, "高级设置"); setCentralWidget(tabs); QSystemTrayIcon *tray = new QSystemTrayIcon(this); tray->setIcon(QIcon(":/new/prefix1/ico")); QMenu *trayMenu = new QMenu(this); trayMenu->addAction("显示主界面", this, &QWidget::showNormal); trayMenu->addAction("退出", qApp, &QApplication::quit); tray->setContextMenu(trayMenu); tray->show();QFileDialog 和 QColorDialog 这类标准对话框不用自己搭,一行静态调用就能拿到结果,返回值的判断方式是看exec()的返回是不是QDialog::Accepted。进度条配 QTimer 是最常见的组合:定时器每触发一次更新一次 value,顺手把时间显示刷新一遍,这套写法在需要"假装在忙"的场合非常好用。托盘菜单要注意程序退出时得先把 tray 隐藏再 quit,否则图标会在任务栏残留一小会儿,虽然不影响功能,演示的时候很显眼。
4. 数据与系统能力:文件、数据库、图像、网络与多线程的落地位置
4.1 文件与配置:QFile、QDir、INI 与 XML 的分工
文件操作章按"建、写、改、删、改名"排了一遍,实际项目里用得最多的是配置读写。QSettings 是首选,但它有个默认行为会让很多人懵:Windows 下不指定格式时走的是注册表,而不是你以为的 ini 文件。
// 显式指定 IniFormat 和路径,否则内容会进注册表 QString path = QCoreApplication::applicationDirPath() + "/config.ini"; QSettings ini(path, QSettings::IniFormat); ini.setValue("database/host", "127.0.0.1"); // 分组/键 的写法 ini.setValue("database/port", 3306); ini.sync(); // 立即落盘,不等析构 QString host = ini.value("database/host", "localhost").toString(); int port = ini.value("database/port", 3306).toInt();value的第二个参数是默认值,养成写默认值的习惯,配置文件被删或者键名改了都不会崩。sync()不是必须的,析构时也会写,但程序异常退出时没 sync 的数据会丢,改完关键配置立刻 sync 更稳。XML 那两节(创建和读取)走的是 QDomDocument,适合层级结构明确的场景,比如导出报表。文件路径统一用QDir::separator()或者干脆用正斜杠,Qt 内部会自动转换,写死反斜杠在跨平台时容易出问题。
4.2 数据库:驱动检查、SQLite 与 ODBC 的两条路
数据库章开头那节"查询数据库驱动"经常被跳过,但它恰恰是最该先写的一段。Qt 的数据库驱动是插件形式,编译时有、发布时不一定带,连接失败的第一嫌疑永远是驱动没加载。
// 先确认驱动可用,再谈连接 if (!QSqlDatabase::isDriverAvailable("QSQLITE")) { qDebug() << "当前可用驱动:" << QSqlDatabase::drivers(); return; } QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(QCoreApplication::applicationDirPath() + "/data.db"); if (!db.open()) { qDebug() << "打开失败:" << db.lastError().text(); return; } QSqlQuery q; q.prepare("INSERT INTO user(name, age) VALUES(?, ?)"); // 占位符防注入 q.addBindValue("A同学"); q.addBindValue(20); if (!q.exec()) qDebug() << q.lastError().text();| 数据库 | 驱动名 | 典型场景 | 注意 |
|---|---|---|---|
| SQLite | QSQLITE | 单机工具、课程设计 | 无需服务端,文件跟着程序走 |
| Access | QODBC | 老系统对接 | 需要先配 DSN,32/64 位要匹配 |
| 其他服务端库 | 对应驱动 | 多人共用数据 | 发布时必须带 sqldrivers 目录 |
SQLite 是课程设计的最优解,零配置、单文件、拷走就能用。用占位符绑定参数这件事别偷懒,字符串拼接在遇到带单引号的内容时直接语法错误,而且没有任何安全余量。查询结果遍历用while (q.next()),取字段用q.value(0)或q.value("name"),后者可读性更好但写错字段名不会报错,只返回空值。
4.3 图形图像与多媒体:QPainter 绘制与效果器
图形那一章的内容密度很高:绘制文字、线条、椭圆,显示静态和动态图,平移、翻转、缩放,图片上加文字,再加模糊、着色、阴影、透明四种效果。前一半靠 QPainter,后一半靠 QGraphicsEffect,两条路别混着用。
// 在窗体的 paintEvent 里画,不要在外面直接 QPainter p(this) void MainWindow::paintEvent(QPaintEvent *) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing); // 抗锯齿,线条才不毛糙 p.setPen(QPen(Qt::darkCyan, 2)); p.drawText(20, 30, "绘制文字"); p.drawLine(20, 45, 220, 45); p.drawEllipse(20, 60, 120, 80); }paintEvent里创建 QPainter 是固定套路,在外面创建会警告"QPainter::begin: Paint device returned engine == 0",因为绘制设备还没准备好。缩放和翻转用QPixmap::scaled()和QTransform的rotate(180),图片加文字则是在 pixmap 上开一个 QPainter 直接画上去。
// 给图片套一层模糊,比手写卷积省事得多 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect; blur->setBlurRadius(8); // 半径越大越糊,8 已经很明显 label->setGraphicsEffect(blur);阴影、着色、透明同理,换成 QGraphicsDropShadowEffect、QGraphicsColorizeEffect、QGraphicsOpacityEffect 即可,参数名都是自解释的。需要注意一个控件只能挂一个效果器,想同时模糊加阴影,得嵌套容器。多媒体那块用 QMediaPlayer 配 QVideoWidget,播放音视频的代码量和播放器按钮差不多,真正的坑在编解码器,换个格式可能就播不了,演示前一定要用实际文件试。
4.4 网络与并发:Socket、QThread 与线程池
网络章覆盖了点对点聊天的服务端和客户端、局域网广播、SMTP 发邮件,还有调用系统接口判断网络状态。前三个用 QTcpSocket、QTcpServer、QUdpSocket 就能实现,通信骨架大同小异:连上、读、写、断开,四个信号。真正需要提前设计的是线程,因为网络和耗时操作都不能放在界面线程里。
// 子类化 QThread:run() 里只算不画,结果用信号送回主线程 class Worker : public QThread { Q_OBJECT protected: void run() override { QString result = doHeavyWork(); // 耗时计算放这里 emit finishedWith(result); // 跨线程信号默认走队列连接 } signals: void finishedWith(const QString &result); };信号槽的跨线程投递是 Qt 帮你做掉的,前提是连接类型为 Auto 且接收者在主线程。最容易出的错是在run()里直接new QWidget或者改 QLabel 的文字,程序不一定立刻崩,但会出现随机崩溃或者界面卡死,而且报错信息往往指向别处,非常难查。另一种写法是 QRunnable 配 QThreadPool,适合大量短任务,代价是 QRunnable 不是 QObject,没有信号,回主线程得用QMetaObject::invokeMethod加队列连接。选哪种?任务数量少、需要独立控制生命周期就 QThread,任务多而短就用线程池。
4.5 系统操作与数据安全:注册表、命令执行与加密
系统操作那几节偏工具性:取屏幕分辨率、取本机名和 IP、按键值判断、读环境变量、执行系统命令。IP 那节容易混,取本机 IP 用 QHostInfo 遍历地址列表,根据域名查 IP 用QHostInfo::fromName(),两者不是一回事。执行系统命令用 QProcess,同步调用要注意别阻塞界面。
加密章给了四种:QByteArray 简单变换、AES、MD5、随机数。MD5 在 Qt 里是一行的事。
// 算 MD5:注意它只适合校验,不适合存密码 QByteArray digest = QCryptographicHash::hash("123456", QCryptographicHash::Md5).toHex(); qDebug() << digest; // 32 位十六进制串AES 那节的情况要说明白:Qt5 本身没有公开的 AES 封装类,范例里的实现通常是引入一个小型加密库或者自己封装分组运算。这意味着密钥长度、分组模式、填充方式都得在文档里写清楚,否则换个人接手根本没法解密。随机数别用qrand(),它的种子不设就固定,同一时刻两次运行结果一样,做密钥或者验证码都是灾难,改用 QRandomGenerator 更稳妥。注册表那节用的还是 QSettings,不指定格式时默认就写注册表,和 4.1 里说的正好是一体两面。
4.6 打包部署:windeployqt 与安装包制作
最后一章是交付环节,也是最容易在答辩现场掉链子的地方。开发机上跑得好好的 exe,拷到别人电脑双击就报"缺少 Qt5Core.dll",原因不是没编译,是动态库没跟着走。
# release 构建之后,用 windeployqt 把依赖补齐 windeployqt --release --no-translations D:\build\QtDemo\release\QtDemo.exe--release对应 release 构建,--no-translations去掉用不上的翻译文件,体积能小不少。执行完 exe 旁边会多出一堆 dll,还有两个目录必须确认存在:platforms里的 qwindows.dll,缺了会报"could not find or load the Qt platform plugin windows";sqldrivers里的 qsqlite.dll,缺了数据库连不上。再往上做成安装包,范例里给了 FilePacker 和 Inno Setup 两种思路,前者按文件列表打自解压包,后者能做带安装向导、写注册表、建快捷方式的正式安装程序,课程设计交前者够用,毕设想要体面一点用后者。
5. 避坑与排查:Qt5 项目里最容易翻车的五个点
5.1 编译与运行期的三个高频问题
程序在自己电脑上能跑,拷到别人电脑就报缺 dll。现象是双击 exe 弹出系统错误框,提示找不到 Qt5Core.dll 或平台插件。原因是开发机 PATH 里有 Qt 的 bin 目录,程序依赖 PATH 找库,而目标机器没有。解决办法是走一遍 windeployqt,并且把 platforms 和 sqldrivers 两个目录一起拷过去,只拷 exe 是不够的。
界面里的控件不显示,或者颜色、字体全不对。现象是窗口出来了,里面空空的,或者背景色把按钮也染红了。原因通常是两种:控件创建了但没给父对象,成了游离的顶层窗口,关掉主窗口它还在;或者样式表写在父容器上,子控件继承了不该继承的属性。解决办法是 new 的时候把 parent 传进去,样式表加选择器限定,比如QWidget#panel { background:red; },只作用于指定对象名。
中文显示成方块或者乱码。现象是界面上的中文变成问号,控制台输出是乱码。原因是源码文件编码和编译器解释编码不一致,MinGW 下一般没问题,换编译器或者换了编辑器默认编码就会翻。解决办法是全项目统一 UTF-8 并加 BOM 或统一无 BOM,取外部文本时用QString::fromLocal8Bit(),写死在代码里的中文字符串用QStringLiteral包一层,不要用裸的const char*。
5.2 部署与交付期的两个高频问题
配置写是写了,换个目录就丢。现象是程序重启后设置回到默认值。原因是路径用了相对写法,而程序的工作目录取决于从哪里启动,从快捷方式启动和从命令行启动拿到的当前目录不一样。解决办法是用QCoreApplication::applicationDirPath()拼绝对路径,并且给value()带上默认值。这一条我吃过不止一次亏,开发时用 Qt Creator 启动,工作目录是构建目录,直接双击 exe 就变成 release 目录,配置正好写在两个地方。
多线程一开就随机崩,日志停在哪一步看不出规律。现象是点开始按钮后程序闪退,或者界面卡住不刷新。原因是子线程里直接操作了 QWidget 或者 QPixmap,这类对象有线程亲和性,只能在创建它的线程里用。解决办法是把耗时逻辑留在子线程,结果通过信号槽或者QMetaObject::invokeMethod投递回主线程再更新界面,别图省事直接改 label 的 text。
5.3 排查顺序:从现象到根因的四步定位
出问题的时候最忌讳东改一版西改一版。我习惯按四层往下走:先看是编译期、链接期、运行期还是部署期。编译报错在语法和头文件,链接报错多半是 .pro 里漏了模块或者漏了源文件,运行期崩在逻辑和线程,部署期崩在依赖。分层之后定位范围立刻缩小。
第二层用 Qt 自带的开关。运行时崩溃先开QT_DEBUG_PLUGINS=1看插件加载过程,缺哪个插件一目了然;数据库连不上就打印QSqlDatabase::drivers();信号没反应就在 connect 之后加一句 qDebug 确认返回值是不是 true。第三层才是打断点和看调用栈,把范围压到具体函数。第四层是复现,能稳定复现的问题都不算难,难的是偶发,偶发问题优先怀疑线程和未初始化变量。
注意:qDebug 的输出在 release 构建里默认还在,交付前要么关掉,要么保留在控制台里,别让它弹到界面上。
6. 验收与答辩:怎么证明这套 Qt5 范例真的跑通了
验收这件事,自己觉得能跑不算数,得有一份可以逐条打勾的清单。下面这张表是我交东西前必过的,按顺序走完,答辩现场基本不会出意外。
| 检查项 | 通过标准 |
|---|---|
| 环境 | 在一台没装过 Qt 的机器上解压即运行,不报缺库 |
| 窗体 | 尺寸固定生效、图标正确、无边框能拖动、多窗体切换正常 |
| 控件 | 至少三种输入类控件加一种视图类控件,并且有联动 |
| 布局 | 拉伸窗口时不错位、不重叠、输入框没有被拉变形 |
| 数据 | 增删改查能落盘,关掉程序重开数据还在 |
| 打包 | 走完 windeployqt,platforms 和 sqldrivers 目录齐全 |
演示顺序也有讲究。别一上来就点最复杂的模块,先跑最短的那条路径:启动、主界面出来、点一个按钮有反应,这三步稳住了再展开。我一般会提前准备一个"最小可运行"的分支,把网络、数据库这些依赖环境的模块做成可选,现场环境不对就切过去,功能少但一定跑得起来,比硬撑着演示一个卡住的界面体面得多。
答辩时最容易被问到的是"这个功能你是怎么实现的",而不是"这个功能是干什么的"。所以每一章至少要能说清一个具体落点:窗体那章说setWindowFlags和鼠标事件的三段式,控件那章说 QRadioButton 的互斥分组,数据那章说占位符绑定参数,多线程那章说信号槽的队列连接。能讲出"为什么这么写、换个写法会怎样",比把功能列一遍有说服力得多。
代码里留好注释,尤其是那些参数不是自解释的地方,比如模糊半径为什么是 8、线程池上限为什么设成 CPU 核数。评委翻代码的时间通常只有几分钟,注释是唯一的后悔药。
从那以后我每次交东西之前,都会先在一台干净机器上双击一次 exe,过不去就先补依赖,再谈功能演示。这个习惯救过我好几次,也希望帮到你。
本文还有配套的精品资源,点击获取