Qt5 C++工控界面开发实战:14章例程索引与打包避坑指南
发布时间:2026/10/29 13:24:31 锦皓数字建站

简介《Qt5范例开发大全完整目录版》是一份面向C开发者与Qt5入门、进阶学习者的PDF电子书围绕桌面应用开发中从环境搭建到发布部署的完整链路展开。内容按14章组织覆盖Qt Creator安装与Hello World、窗体与控件布局、组件应用、文件操作、图形图像与多媒体、系统操作与注册表、数据库基础、网络开发、进程线程、数据安全及打包部署等主题并以大量可参照的实例串联知识点便于读者按章节查漏补缺或对照实践。压缩包内共1个PDF文件大小约2.84MB完整目录版结构清晰适合快速定位具体控件、模块与开发场景。现有2459人学习可作为Qt5桌面开发过程中查阅API用法、界面布局思路和工程部署方法的常备资料。1. 一份 14 章例程目录能提供什么帮助先看清它覆盖的边界接手一个几年前写的 Qt5 C 工控界面最耗神的往往不是业务逻辑而是那种控件位置全靠手写像素的窗体动一个按钮整页对齐全崩。这类活儿缺的不是架构能力而是一份能直接对照着抄的例程索引——某个需求该用哪个类、参数在哪设、边框怎么去掉、图标为什么加载不出来。《Qt5 范例开发大全完整目录版.pdf》走的就是这个路子14 章从开发环境、窗体基类、常用控件一路铺到文件操作、图形图像、多媒体、系统操作、注册表、数据库、网络、进程线程、数据安全和打包部署目录本身就是一张“需求—实现”对照表。它适合已经会写 C、准备把 Qt5 桌面程序从“能跑”推到“能交付”的人也适合当排错时的第一站先按目录定位章节再看那段例程怎么写的比从官方文档里一页页翻快得多。接下来按实际落地的顺序拆——环境骨架、窗体控件、文件数据、避坑、打包。2. 环境与骨架从 Qt Creator 到第一个能跑的 .pro 工程2.1 装完先别急着写业务Hello World 跑通再谈别的Qt5 的环境配置有几个和老版本不一样的地方第一次装最容易卡在“编译按钮是灰的”或者“命令行敲 qmake 提示找不到”。按下面的顺序走一遍能省掉大量来回试错的时间。安装包选带编译器的离线版本例如 Qt 5.1.1 for Windows 32-bit编译器选 MinGW。版本号对齐很重要Qt 库、MinGW、Qt Creator 三者版本错配会直接导致链接失败。安装路径不要带空格、中文和括号。C:\Qt\Qt5.1.1这类纯英文短路径最稳后面配环境变量、写 pro 文件都少一堆转义问题。把Qt安装目录\5.1.1\mingw48_32\bin加进系统 PATH注意和前一项之间用分号隔开。这一步决定了你能否在任意目录下直接执行 qmake也决定了后面打包时 windeployqt 能不能被找到。打开 Qt Creator新建 Qt Gui 应用基类先选 QMainWindow构建套件选 Desktop Qt 5.1.1 MinGW然后直接编译运行。第一个工程的main.cpp保持自动生成的骨架就行重点看它做了哪几件事#include mainwindow.h #include QApplication int main(int argc, char *argv[]) { QApplication a(argc, argv); // argc/argv 传给 Qt用于解析 -style 这类命令行参数 MainWindow w; w.show(); // show 只是把窗口标记为可见此时尚未进入事件循环 return a.exec(); // 进入事件循环窗口开始响应鼠标键盘退出时返回状态码 }a.exec()这一行是新手最容易忽略的少了它窗口会一闪而过因为main函数执行完就直接返回了栈上的MainWindow对象随之析构。argc、argv也不是摆设Qt 自己的命令行参数解析、高 DPI 开关都依赖它别图省事写成QApplication a(argc, argv)之外的形式。主窗口里放一个标签验证控件创建和坐标定位#include mainwindow.h #include QLabel MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(Qt5 示例); resize(480, 320); QLabel *label new QLabel(this); // 指定父对象随窗口一起销毁 label-setText(Hello World); label-setGeometry(100, 80, 200, 40); // 四个参数依次是 x、y、宽、高 }setGeometry的坐标系原点在父窗口客户区左上角不含标题栏。四个参数是绝对像素值窗口一缩放它不会跟着动这也是后面要换布局管理器的原因。new QLabel(this)里的this让 Qt 的对象树接管内存窗口析构时子控件自动释放不需要手写 delete。2.2 QMainWindow、QWidget、QDialog三个基类选错后面全是返工新建项目时那个三选一的对话框很多人随手点第一个后面想加菜单栏才发现选错了基类。三者的定位差别很明确基类自带结构是否顶级窗口典型用途QMainWindow菜单栏、工具栏、状态栏、中央部件区是主界面、带菜单的桌面应用QWidget无纯空白部件可作为顶级窗口也可嵌进别的容器自定义控件、无边框窗口、嵌入页QDialog默认按钮区支持模态是登录框、设置页、确认提示选型判断其实看一件事这个窗口以后会不会长出菜单栏和状态栏。会就选 QMainWindow它的setCentralWidget已经把中央区域和周边栏位的关系处理好了不会选 QWidget 最轻没有多余的布局开销需要“弹出来先处理完再往下走”的交互选 QDialogexec()会阻塞在那一行直到用户关闭。QDialog 的模态有两种写法差别在事件循环// 模态exec() 内部另起事件循环阻塞当前函数返回值是对话框的退出码 LoginDialog dlg(this); if (dlg.exec() QDialog::Accepted) { // 用户点了确定 } // 非模态show() 立即返回对话框和主窗口可以同时操作 LoginDialog *dlg new LoginDialog(this); dlg-setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动释放避免内存越积越多 dlg-show();多窗体调用里最常见的翻车是栈上对象配show()函数一返回对话框对象析构窗口瞬间消失。要非模态就用堆对象加WA_DeleteOnClose要模态就用栈对象加exec()别混着来。2.3 .pro 文件里那几个路径参数决定了你编译快不快qmake 工程文件是 Qt 构建的入口默认生成的.pro只有几行够跑 Hello World但撑不住一个多模块项目。下面是实际项目里我会加的部分QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets # Qt5 把 widgets 从 gui 里拆出来了 TARGET QtDemo TEMPLATE app SOURCES main.cpp \ mainwindow.cpp HEADERS mainwindow.h CONFIG c11 # 中间产物和可执行文件分目录debug/release 互不污染 CONFIG(debug, debug|release) { DESTDIR $$PWD/bin/debug } else { DESTDIR $$PWD/bin/release } OBJECTS_DIR $$OUT_PWD/obj MOC_DIR $$OUT_PWD/moc RCC_DIR $$OUT_PWD/rcc UI_DIR $$OUT_PWD/uiQT widgets那行必须带greaterThan判断否则同一份 pro 拿回 Qt4 环境会因为找不到 widgets 模块直接报错。DESTDIR控制 exe 输出位置默认会散落在 debug/release 子目录里打包脚本要写死路径就很别扭统一收进 bin 下省事。MOC_DIR、RCC_DIR、UI_DIR是把 moc、rcc、uic 生成的中间文件挪出源码目录好处是源码树干净坏处是你改完带Q_OBJECT的头文件后如果只做增量编译没生效得手动执行一次 qmake 再构建——这是后面避坑章节要展开的一条。改完 pro 文件Qt Creator 里右键项目选“执行 qmake”再“重新构建”。直接点构建有时候不会重新读 pro改了半天配置没生效白折腾。3. 窗体与控件无边框拖动、qrc 图标和布局的落地顺序3.1 无边框窗体拖动globalPos 与 pos 的差别就是那条拖拽残影setWindowFlags(Qt::FramelessWindowHint)之后系统不再接管标题栏拖动鼠标事件全部落回窗口自己身上必须手动处理按下、移动、释放三个事件。这段代码看起来简单写法不对就会出现窗口“跳一下再跟手”或者越拖越偏。// mainwindow.h 片段 protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: QPoint m_dragOffset; // 按下瞬间鼠标相对窗口左上角的偏移 bool m_dragging false;// mainwindow.cpp 片段 void MainWindow::mousePressEvent(QMouseEvent *e) { if (e-button() Qt::LeftButton) { m_dragging true; m_dragOffset e-globalPos() - frameGeometry().topLeft(); e-accept(); } QMainWindow::mousePressEvent(e); } void MainWindow::mouseMoveEvent(QMouseEvent *e) { if (m_dragging (e-buttons() Qt::LeftButton)) { move(e-globalPos() - m_dragOffset); // 用全局坐标减偏移得到窗口新位置 e-accept(); } QMainWindow::mouseMoveEvent(e); } void MainWindow::mouseReleaseEvent(QMouseEvent *e) { m_dragging false; QMainWindow::mouseReleaseEvent(e); }关键在m_dragOffset这个量。e-globalPos()是鼠标在屏幕上的绝对坐标frameGeometry().topLeft()是窗口边框左上角在屏幕上的位置两者相减得到“鼠标点在窗口内的哪个位置”。移动时用新的全局坐标减去这个偏移窗口就跟着走且鼠标始终停在窗口内同一点不会跳。常见错误写法是每次移动都拿e-globalPos()和上一次的last做差再累加last的更新时机一旦放在move()之前或之后不一致误差就会累积表现为越拖越偏。另外e-buttons()用的是复数形式判断的是“当前哪些键处于按下状态”e-button()判断的是“触发这个事件的是哪个键”移动事件里必须用前者。setWindowFlags有个坑在窗口已经show()之后再调用窗口会先隐藏需要重新show()一次否则看起来像程序卡死了。稳妥做法是在构造函数里、show()之前设置完。做异形窗口圆形、不规则边框时还要加setAttribute(Qt::WA_TranslucentBackground)同时给窗口画背景的paintEvent里用QPainterPath裁剪不然透明区域会残留黑底。3.2 标题栏图标与 .qrc 资源路径写错一次就够记一辈子标题栏图标要求不高16×16 或 32×32 的 ICO 就够但资源路径写错是高频翻车点。正确做法是先建.qrc资源文件把图片编译进二进制这样发布时不依赖外部文件。RCC qresource prefix/icons file aliasapp.icores/app.ico/file /qresource /RCCprefix是虚拟目录前缀alias是编译后的别名不写 alias 就会保留原文件名。上面这段编译后引用路径是:/icons/app.icosetWindowTitle(Qt5 窗体); setWindowIcon(QIcon(:/icons/app.ico)); // 冒号开头表示从资源系统读取资源系统的原理是 rcc 工具把文件按字节数组编进一个 C 源文件链接期并入可执行文件所以运行时不需要带着图片。由此带来两个注意点一是改了.qrc内容必须重新执行 qmake否则新增的文件不会被编译进去运行时报“找不到资源”二是资源路径区分大小写Windows 文件系统不区分但 qrc 里的别名区分App.ico和app.ico是两个东西。ICO 文件本身也有讲究单尺寸的 16×16 在高分屏任务栏上会被拉伸得发虚用带 16/32/48 多尺寸的 ICO 更稳。图标显示不出来时先确认QIcon的路径有没有拼错再确认 qrc 是否真的被编译——可以在qDebug()里打印QFile(:/icons/app.ico).exists()的返回值一行代码定位问题。3.3 布局不是可选件QGridLayout QTabWidget 组一个配置页手写坐标在演示程序里能跑一旦要适配不同分辨率和系统字体就会集体错位。Qt 的布局管理器负责在窗口尺寸变化时重新计算子控件几何代价是不能再随意setGeometry。下面是一个典型的网络配置页两行三列QWidget *page new QWidget(this); QGridLayout *grid new QGridLayout(page); QLabel *lblHost new QLabel(地址, page); QLineEdit *edtHost new QLineEdit(127.0.0.1, page); QLabel *lblPort new QLabel(端口, page); QSpinBox *spnPort new QSpinBox(page); spnPort-setRange(1, 65535); // 端口合法范围超出范围控件自己会拦住 spnPort-setValue(8080); grid-addWidget(lblHost, 0, 0); // 第 0 行第 0 列 grid-addWidget(edtHost, 0, 1, 1, 2); // 第 0 行第 1 列占 1 行 2 列 grid-addWidget(lblPort, 1, 0); grid-addWidget(spnPort, 1, 1); grid-setColumnStretch(1, 1); // 第 1 列吸收多余宽度输入框随窗口拉伸 grid-setContentsMargins(12, 12, 12, 12); // 左、上、右、下留白addWidget的后四个参数是可选的rowSpan、columnSpan上面让地址输入框横跨两列视觉上和端口行对齐。setColumnStretch决定窗口变宽时多出来的空间分给谁不设的话所有控件挤在左边右边一大片空白。setContentsMargins控制布局和父容器边缘的距离默认值不为零想让内容贴边得显式设成 0。页面多了就用 QTabWidget 分页QTabWidget *tabs new QTabWidget(this); tabs-addTab(buildNetworkPage(), 网络); tabs-addTab(buildStoragePage(), 存储); tabs-setMovable(true); // 允许用户拖动页签换顺序 setCentralWidget(tabs); // QMainWindow 的中央区域交给页签控件布局代码写多了会啰嗦实际项目里我一般混合着来结构用 Designer 拖生成ui_xxx.h动态生成的控件和信号连接用代码写在构造函数末尾。纯 Designer 的缺点是改动不直观、代码评审看不到界面变化纯代码的缺点是调间距全靠试。混合方案里UI_DIR指向独立目录的好处这时候就出来了——生成的ui_*.h不混在源码树里提交代码时不会误传。3.4 信号槽的三种连法以及什么时候必须用 Qt::QueuedConnection信号槽是 Qt 区别于其他 C 界面库的核心机制编译期由 moc 生成元对象代码运行时靠元对象系统做参数匹配和调用分发。三种连法各有适用面// 1) 宏写法老代码常见参数用字符串拼错只有运行时才在控制台报警告 connect(btnClose, SIGNAL(clicked()), this, SLOT(close())); // 2) 函数指针编译期类型检查新代码首选 connect(spnPort, static_castvoid(QSpinBox::*)(int)(QSpinBox::valueChanged), this, MainWindow::onPortChanged); // 3) lambda临时逻辑不污染头文件捕获 this 时注意对象生命周期 connect(btnClose, QPushButton::clicked, this, [this]() { if (QMessageBox::question(this, 确认, 确定退出) QMessageBox::Yes) close(); });第二种写法里那个static_cast是因为valueChanged有int和QString两个重载不指明取哪个编译器无法推导。Qt 5.7 之后可以用QOverloadint::of(QSpinBox::valueChanged)简化但项目正文里的环境是 5.1.1用static_cast兼容性更好。连接类型默认是Qt::AutoConnection同线程内退化成直连跨线程自动切成队列连接。需要显式指定Qt::QueuedConnection的场景通常有两个一是信号从工作线程发出、槽函数操作界面必须走队列把调用排到主线程事件循环里二是同一线程内想延迟执行又不方便用QTimer::singleShot。反过来Qt::BlockingQueuedConnection只允许跨线程用同线程用会直接死锁——这条我踩过程序卡住不报错调试半天才定位到连接类型。还有一点connect的返回值别丢。返回false说明信号或槽没匹配上常见原因是槽函数没加slots关键字、参数类型对不上、或者对象在连接时还没构造完。养成判断返回值的习惯能省掉大量“点了没反应”的排查。4. 文件与数据INI、XML、SQLite 和加密各写一遍4.1 QSettings 读写 INI分组、编码和写入时机配置持久化最省事的方案是 QSettings。Windows 上它默认写注册表想落到 ini 文件必须显式指定格式这一点目录里那章“INI 文件写入/读取”讲得很清楚但实际用起来还有几个细节。// 文件放在 exe 同级目录便携式部署常用这种布局 QString iniPath QCoreApplication::applicationDirPath() /config.ini; QSettings ini(iniPath, QSettings::IniFormat); ini.setIniCodec(UTF-8); // 不设的话中文值会变成转义序列 ini.beginGroup(network); ini.setValue(host, 192.168.1.10); ini.setValue(port, 8080); ini.endGroup(); ini.sync(); // 显式落盘别等析构 ini.beginGroup(network); QString host ini.value(host, 127.0.0.1).toString(); // 第二个参数是默认值 int port ini.value(port, 80).toInt(); ini.endGroup();beginGroup/endGroup对应 ini 里的节名嵌套 group 会生成[network/proxy]这种层级节。value()的第二个参数是默认值配置文件缺失或键不存在时返回它比先判断contains()再取值简洁。sync()负责把内存缓冲写回磁盘不调用的话要等 QSettings 析构才落盘程序异常退出就丢了。setIniCodec(UTF-8)这行在 Qt5 里有效作用是让读写 ini 时按 UTF-8 编解码不加的话中文会以\xXXXX形式存储人眼不可读换台机器用记事本打开就是乱码。如果用 QSettings 存的是注册表这行没有效果因为注册表本身是 Unicode 的。4.2 QDomDocument 处理 XML节点遍历与中文乱码XML 适合存结构比 ini 复杂的配置比如带层级的设备列表。QDomDocument 把整个文档加载成 DOM 树读写都直观缺点是文件大了内存占用高几十 MB 以上的场景要换 QXmlStreamReader 流式解析。QDomDocument doc; QDomElement root doc.createElement(config); doc.appendChild(root); QDomElement item doc.createElement(item); item.setAttribute(key, host); item.appendChild(doc.createTextNode(192.168.1.10)); root.appendChild(item); QFile f(config.xml); if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(f); out.setCodec(UTF-8); // 必须设否则中文按本地编码写出 doc.save(out, 4); // 第二个参数是缩进空格数 }读取侧要判断解析结果别假设文件一定合法QFile f(config.xml); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; QDomDocument doc; QString errMsg; int errLine 0; if (!doc.setContent(f, errMsg, errLine)) { qWarning() XML 解析失败 errLine errMsg; return; } QDomElement root doc.documentElement(); for (QDomElement e root.firstChildElement(item); !e.isNull(); e e.nextSiblingElement(item)) { QString key e.attribute(key); QString val e.text(); }setContent返回false时errMsg和errLine会给出具体行号和原因排查手写 XML 的标签闭合问题很有用。遍历用firstChildElement/nextSiblingElement只走元素节点不会把空白文本节点也遍历进来比firstChild()干净。4.3 SQLite 从建表到 QTableViewQSqlTableModel 省掉的样板代码本地结构化数据用 SQLite 最合适零配置、单文件、Qt 自带驱动。连接和建表QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(QCoreApplication::applicationDirPath() /data.db); if (!db.open()) { qWarning() 打开数据库失败 db.lastError().text(); return; } QSqlQuery q(db); q.exec(CREATE TABLE IF NOT EXISTS device ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, ip TEXT)); q.prepare(INSERT INTO device (name, ip) VALUES (?, ?)); // 占位符防注入 q.addBindValue(PLC-01); q.addBindValue(192.168.1.20); if (!q.exec()) qWarning() q.lastError().text();界面展示不需要手写SELECT再逐行塞进表格QSqlTableModel 直接把表映射成模型QSqlTableModel *model new QSqlTableModel(this, db); model-setTable(device); model-setEditStrategy(QSqlTableModel::OnManualSubmit); // 改完手动 submit避免误改即落库 model-select(); model-setHeaderData(1, Qt::Horizontal, 设备名); model-setHeaderData(2, Qt::Horizontal, IP); ui-tableView-setModel(model);OnManualSubmit适合需要“编辑完统一保存”的场景配合model-submitAll()使用OnFieldChange是改一个字段立刻写库适合实时性要求高的配置。用 QODBC 连 Access 时连接串要写全驱动名和文件路径且必须在系统里装好对应的 ODBC 驱动否则open()返回 falselastError()会提示驱动未找到——这条比代码本身更容易卡住人。4.4 MD5、QByteArray 与 AES哪些能自己写哪些必须挂 OpenSSL摘要和编码用 Qt 自带的类就够QByteArray hash QCryptographicHash::hash(123456.toUtf8(), QCryptographicHash::Md5); qDebug() hash.toHex(); // 32 位十六进制字符串 QByteArray raw QByteArray::fromHex(48656c6c6f); QByteArray b64 raw.toBase64(); // 网络传输、配置文件里存二进制常用MD5 不可逆但网上有大量彩虹表直接存密码哈希等于没加密。正确做法是加盐再哈希盐值每个用户独立随机生成和哈希一起存库。SHA1 同理新项目建议用 SHA-256QCryptographicHash里直接换枚举值即可。AES 是另一回事Qt5 核心模块没有提供对称加密类目录里那章“AES 加密数据”的实现必然依赖第三方库常见的是 OpenSSL。用的时候 pro 里加LIBS -lcrypto、INCLUDEPATH指向头文件目录运行时还要把libeay32.dll、ssleay32.dll一起带上32 位和 64 位不能混用。这一段我建议先跑通再谈参数AES 的密钥长度、分组模式、填充方式、IV 传递方式任一项对不上解密出来就是乱码而且不报错——这种问题没有捷径只能两边打印十六进制逐段比对。随机数方面qrand()在 Qt5 里能用但已不推荐QRandomGenerator::global()-generate()是 5.10 之后的正路。生成密钥、盐值、会话 ID 这类安全用途别用固定种子的qrand()同一时刻启动的多个实例会拿到相同序列。5. 避坑与排查五个当场翻车的 Qt5 细节5.1 编译与链接阶段的报错现象报QWidget: No such file or directory但头文件明明在。原因pro 文件里只写了QT core guiQt5 把 widgets 模块拆出去了。解决加greaterThan(QT_MAJOR_VERSION, 4): QT widgets执行 qmake 后重新构建。现象链接报undefined reference to vtable for XxxClass。原因类里声明了Q_OBJECT宏但 moc 没有为它生成代码通常是头文件没加进HEADERS或者改完头文件只做了增量编译。解决确认头文件在HEADERS列表里右键执行 qmake再清理重建。现象改了.qrc里的图片程序里还是旧的。原因qrc 内容变更不会自动触发 rcc 重新编译。解决执行一次 qmake或者手动删掉构建目录下的rcc子目录再构建。5.2 运行阶段的问题现象源码里的中文在界面上显示成乱码或问号。原因源文件编码、编译器输入编码、QString 转换三者不一致MinGW 默认按本地编码处理窄字符串。解决源文件统一存 UTF-8pro 里加QMAKE_CXXFLAGS -finput-charsetUTF-8界面字符串用QStringLiteral(中文)包起来读 ini 记得setIniCodec读 XML 记得QTextStream::setCodec。现象Debug 下运行正常拷到别的机器双击 exe 一闪而过没有任何提示。原因缺 Qt 运行库或者缺平台插件qwindows.dll。解决用windeployqt把依赖收齐插件目录结构是platforms/qwindows.dll不能只拷 dll 不建目录。排查时在命令行里运行 exe缺什么 DLL 系统会直接报出来。5.3 数据库与资源的隐性失败现象QSqlDatabase::addDatabase(QSQLITE)之后open()返回 falselastError()提示驱动未加载。原因发布时只拷了 exe 和 Qt 核心库漏了sqldrivers/qsqlite.dll。解决把整个sqldrivers目录按原结构拷到 exe 同级plugins路径不要打散。开发机上能跑、客户机上不行九成是插件目录结构被破坏。现象托盘图标、窗口图标在开发机上正常打包后变成空白。原因图标走的是外部文件路径而不是 qrc 资源打包时文件没带上。解决图标一律进 qrc引用写成:/开头确实要用外部文件的路径基于QCoreApplication::applicationDirPath()拼不要用相对路径。这些问题的共同点是编译器不报错运行时也不崩溃只是“表现得不对”。Qt 的错误信息大多走qWarning()输出到控制台GUI 程序默认没有控制台窗口什么都看不到。我的习惯是开发期在 pro 里加CONFIG console让 qDebug 有地方输出发布前再去掉。这一步加上去上面一半的问题能自己定位。6. 打包验证windeployqt 到安装包的最后一公里6.1 windeployqt 的调用顺序Release 构建完成后进到 exe 所在目录执行windeployqt --release --no-translations --no-system-d3d-compiler QtDemo.exe--release告诉工具按 release 版 Qt 库收集依赖不加会按 debug 收打出来的包体积翻几倍还跑不起来。--no-translations跳过语言包--no-system-d3d-compiler跳过 D3D 编译器界面程序用不上能少几 MB。执行完目录里会多出platforms、imageformats、sqldrivers等文件夹这些目录结构不能动Qt 是按相对路径找插件的。如果程序用了 OpenSSL、数据库驱动或多媒体后端windeployqt 不一定能全部识别得手动补。补完之后在一台没装过 Qt 的机器上双击跑一遍缺什么补什么这是最可靠的验证方式别信开发机上的结果。6.2 安装包与交付前的检查项用 Inno Setup 做安装包时[Files]段把整个输出目录递归包含进去就行[Files] Source: bin\release\*; DestDir: {app}; Flags: recursesubdirs createallsubdirs [Icons] Name: {group}\QtDemo; Filename: {app}\QtDemo.exerecursesubdirs保证 plugins 子目录一起打进去createallsubdirs保证安装时按原结构建目录。这两项漏一个装完就是白屏或者数据库打不开。交付前我会固定走一遍这张表检查项方法常见缺失平台插件看platforms/qwindows.dll是否存在整个目录被漏拷数据库驱动看sqldrivers/下对应 dll只拷了 qsqlite漏了 qodbc图片格式打开带 jpg/png 的界面imageformats目录缺失配置文件首次启动是否自动生成 ini程序目录无写权限高分屏125% 缩放下看控件是否错位没开AA_EnableHighDpiScaling最后一条提一下Qt5 的高 DPI 支持要在main里、构造 QApplication 之前设置QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling)放在show()之后就没用了。这个属性开不开界面在 4K 屏上完全是两个样子。从那以后我每次出安装包前都会找一台干净的机器完整装一遍、点一遍主要功能把缺的 DLL 补进依赖清单再重新打包。开发机上一切正常不等于交付版本能用这一步省不得。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。