ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

QT5源码实战:从环境搭建到模块复用的完整指南

QT5源码实战:从环境搭建到模块复用的完整指南 简介本资源是《QT5开发及实例》一书的配套源代码包面向C初学者、Qt入门开发者及高校相关课程学习者旨在通过实例驱动方式系统掌握Qt 5跨平台GUI应用开发技术。压缩包共1455个文件大小39.51MB涵盖255个.cpp源文件、242个.zbak备份文件、104个.h头文件、71个.pro工程文件、71个.ui界面文件及大量debug/release编译产物和可执行exe文件完整复现书中全部小实例与三大综合项目如DialogExample、TreeWidget、DockWindows等支持开箱即用与分模块调试。目前已有104人下载学习所有代码均按章节组织辅以标准Qt工程结构与常用构建配置makefile、qrc、ts、qm等便于理解信号槽机制、容器迭代器、模型视图、多线程及国际化等核心开发实践。1. 这套QT5配套源代码到底能给你省多少事说实话第一次拿到这套QT5开发及实例配套源代码的时候我并没有太当回事。原因很简单市面上带源码的书和教程太多了很多示例跑起来就废连里面几个类的继承关系都没讲清楚。但后来我在做几个实际项目时反复翻它才发现这套源码有一个其他教程没有的特质——它是按照“从跑通到改造”的顺序组织的前一半是语法和控件后面直接是串口、网络、数据库这些能落地的东西。这篇文章我就结合自己折腾这些实例源码的过程把里面的关键模块、环境配置、常见坑和复用思路讲一遍。目标读者有两类一类是刚接触QT5、想找个有源码的项目快速上手的同学另一类是已经写了几年业务但需要从示例源码里挖可复用模块的开发者。1.1 源码不是拿来粘的是用来拆的我见过太多人拿到一套源码第一反应是全文搜索某个功能然后ctrlc、ctrlv拼进自己项目编译报error就放弃。这种用法浪费了源码80%的价值。我的做法分三步先编译跑通再打断点看调用链最后动手改一个行为。举个实际例子我当年第一次看这套源码里的“串口助手”demo直接把QSerialPort的配置代码抄走结果在自己的QMainWindow里死活收不到数据。后来才发现demo里用的是QSerialPort一个独立对象指针我在自己代码里写成了栈上对象线程和事件循环的时序完全不对。这种问题如果不看调用链单纯复制粘贴是永远发现不了的。所以这篇文章里我提到每个模块都会先讲它是怎么工作的再讲代码怎么用。这样你在自己项目里碰到问题才能顺着原理去定位而不是到处问人。1.2 我从这套源码里扒出来的实用模块清单这套源码虽然叫“QT5开发及实例配套源代码”但它覆盖的东西远不止一个hello world。我按照自己实际项目里用到的频率列了个清单模块核心类/库我的实际应用场景文件拖拽QDragEnterEvent / QDropEvent批量导入图片、日志文件快速打开串口通信QSerialPort / QSerialPortInfo下位机调试工具、仪器数据采集二维码QZXing 或 qrencode设备编号二维码、工单流转多线程QThread / QtConcurrent图片压缩、大文件解析数据库QSqlDatabase / QSqlTableModel本地台账、历史记录查询网络请求QNetworkAccessManager版本检查、接口对接图表绘制Qt Charts波形显示、统计报表这七个方向里至少有五个是我在真实项目里直接复用过思路的。如果你拿到手先别急着一个个读优先看串口、多线程、数据库这三个因为它们最能看出一个写示例的人到底有没有做过真实项目。后面我会单独拆其中几个讲。2. 环境搭建这一关版本选型与第一行代码拿到源码第一步不是打开IDE而是把环境装对。这一关我觉得是整个学习曲线里最劝退的因为你在教程里看到“下一步下一步”到自己电脑上总会冒出各种玄学问题。2.1 Qt 5.12还是5.15不同项目的选型逻辑每次有人问我学QT5该装哪个版本我都会反问一句你最终要交付到哪个环境如果你是做Windows桌面工具、学习为主、以后可能往软件公司走直接选Qt 5.15.2 LTS。它是最后一代完全不依赖新编译器的Qt 5系列社区资料最多网上遇到的坑基本都能搜到答案。如果后续要切到Qt 65.15也是从经典过渡到新架构最平滑的跳板。如果你是在做嵌入式ARM设备上的界面反而别追求太新版本很多板子出厂SDK里默认就是5.12甚至更老的5.9。源码层面看5.12到5.15的API改动很小但你在板上可能要自己编译Qt库编译器版本、交叉编译工具链能不能匹配往往比功能版本更重要。还有一个关键点现在很多人一上来就装Qt 6然后拿旧示例源码编译发现一堆模块改名、API参数变了顿时失去信心。既然项目明确是QT5那就老老实实锁死5.x系列别用6的思维去看5的代码。2.2 编译器与套件配置MSVC和MinGW怎么选这一节可能是问得最多的。安装Qt时你会看到多个套件其中MSVC和MinGW是最常见的两种很多人不明白有什么区别。我用一个简化比喻来解释MSVC是微软的编译器编译出来的程序在Windows上跟系统集成度更高但换一台机器可能需要装对应版本的Visual C运行库。MinGW是一套开源编译工具链编译出的程序通过Qt自带的库运行部署更轻但不方便用某些Windows专属的SDK。我做项目的习惯是给企业内部做工具、后期要做Windows API调用或系统级功能选MSVC做开源小工具、希望拷贝给同事能直接运行选MinGW。源码示例本身没有编译器限制但编译时你选择的套件务必和代码里使用的第三方库一致尤其是串口和二维码相关的库混着用会出现链接错误。2.3 最小可运行工程别一上来就开大项目拿到源码我建议你做的第一件事是建一个空项目把最小结构跑通再往里面加源码示例。我这里给一个最简单的主程序骨架一个空的QMainWindow#include QApplication #include QMainWindow #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow w; w.setWindowTitle(QStringLiteral(QT5 最小工程)); w.setMinimumSize(800, 600); QLabel *label new QLabel(QStringLiteral(源码示例加载窗口), w); w.setCentralWidget(label); w.show(); return app.exec(); }对应的.pro文件只需要三行QT core gui widgets TARGET minimal TEMPLATE app SOURCES main.cpp这个最小工程的意义是验证三件事你的Qt库能正常编译、运行库能加载、调试器能断点。如果这一步都过不了后面打开任何大工程都会把问题混在一起排查成本会倍增。我第一次接触QT5就是犯了这个错误直接从网上下了一个功能丰富的上位机源码编译了半个小时出了两百多个错误后来发现是编译器套件选错了。从那以后任何新环境我都是先跑这个最小工程这习惯一直用到现在。3. 配套源码的工程结构从单文件demo到分层项目示例源码最大的价值之一是给你展示了不同场景下项目应该如何组织。我读这套源码时印象最深的是它开头几个工程非常简单后面却慢慢出现分层结构。3.1 源码的目录组织方式一套比较理想的QT5示例源码目录通常是这样的qt5_demo_source/ ├── 01_controls/ # 控件基础 │ ├── main.cpp │ ├── widget.cpp │ └── widget.h ├── 02_layout/ # 布局管理 ├── 03_dialog/ # 对话框 ├── 04_event/ # 事件与拖拽 ├── 05_serial/ # 串口通信 ├── 06_network/ # 网络请求 ├── 07_database/ # 数据库 ├── 08_thread/ # 多线程 ├── 09_qrcode/ # 二维码 ├── common/ # 通用工具类 └── README.md这套结构的核心思想是“按功能目录隔离示例而不是按工程堆在一起”。每个目录都是一个独立的.pro工程能单独编译运行。这样做的直接好处是你想研究串口时不用打开一个包含数据库和网络代码的大项目编译速度快问题定位也快。我给自己的源码库也沿用这个思路。就算一个项目里需要同时用到串口和二维码也要求把公共组件放在common目录里业务示例按功能拆分别全部塞进一个巨大的cpp文件。3.2 信号槽机制源码里最核心的脉络读这套代码你会发现几乎每个项目都在用connect把界面和逻辑连起来。信号槽就是Qt的灵魂。举个最简单的例子一个按钮点击处理// MyWidget.h class MyWidget : public QWidget { Q_OBJECT public: explicit MyWidget(QWidget *parent nullptr); signals: void dataReady(const QString data); private slots: void onBtnClicked(); }; // MyWidget.cpp connect(ui-btnSend, QPushButton::clicked, this, MyWidget::onBtnClicked); // 某处发射自定义信号 emit dataReady(QStringLiteral(data));我第一次看这个机制时觉得麻烦但后来才明白它解决的是“谁发生变化谁要响应变化”的解耦问题。工作线程完成计算后不需要直接去调用界面控件的函数而是发一个信号界面槽函数在事件循环里执行这样就不会跨线程直接操作UI导致崩溃。读示例源码时我推荐你先找到每个类的connect列表把信号到槽的连接关系画在纸上然后再看具体逻辑。这样可以避免被实现细节牵着走。3.3 从qmake迁移到CMake的经验这套源码里大部分工程用的是qmake这对学习来说完全没问题。但如果你要进公司做正式项目很多团队已经切换到CMake了。qmake和CMake的差别我在实操中的体感是qmake配置简单写.pro文件像写配置清单CMake刚开始写起来啰嗦但好在构建系统更通用尤其是多平台交叉编译、外部依赖管理CMake明显更灵活。把示例从qmake迁到CMake通常只需要写一个最小CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(qt5_demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt5 REQUIRED COMPONENTS Widgets SerialPort Network Sql) add_executable(qt5_demo main.cpp widget.cpp widget.h ) target_link_libraries(qt5_demo Qt5::Widgets Qt5::SerialPort Qt5::Network Qt5::Sql )这里有个特别容易踩的坑AUTOMOC必须开启否则带Q_OBJECT宏的头文件生成的meta-object代码不会自动构建链接时会报一堆未定义符号。很多从qmake切到CMake的人第一眼看到错误完全摸不着头脑其实原因就在这一行。4. 源码里最值得反复看的五个功能模块现在进入正题。这套源码里我实际复用率最高的五个功能模块我一个个拆给你看。4.1 文件拖拽qt5无法拖拽文件的原因和修复“qt5无法拖拽文件”是搜索热词里非常靠前的一个问题我自己也中过招。现象是窗口能正常显示但把文件拖上去没有任何反应。大多数情况下原因就两个一是窗口没有开启接受拖拽二是拖拽事件没被正确的控件接收。很多示例源码里其实写了后一个关键点但新手往往只复制了拖入事件处理函数漏掉了最前面的setAcceptDrops。MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 关键告诉窗口接受拖拽 setAcceptDrops(true); } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event-mimeData()-hasUrls()) { event-acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *event) { const QListQUrl urls event-mimeData()-urls(); for (const QUrl url : urls) { QString filePath url.toLocalFile(); if (!filePath.isEmpty()) { ui-listWidget-addItem(filePath); } } }还有一个更隐蔽的坑如果你的窗口里放了子控件比如一个QLabel或者QListWidget默认事件会先到达子控件而不是主窗口。这时就算主窗口setAcceptDrops(true)也可能没反应。解决办法是给子控件也开启setAcceptDrops或者用事件过滤器把事件转发给主窗口处理。源码里如果只演示了主窗口版本你自己扩展时就要留意这一点。4.2 串口通信从打开端口到收到第一帧数据做硬件相关项目的开发者对串口模块的依赖度极高。这套源码里有一个标准的串口示例我基于它开发过一个下位机调试工具稳定运行了大半年。核心代码其实不长#include QSerialPort #include QSerialPortInfo QSerialPort *serial new QSerialPort(this); void Widget::openPort() { serial-setPortName(ui-comboPort-currentText()); serial-setBaudRate(QSerialPort::Baud9600); serial-setDataBits(QSerialPort::Data8); serial-setParity(QSerialPort::NoParity); serial-setStopBits(QSerialPort::OneStop); serial-setFlowControl(QSerialPort::NoFlowControl); if (serial-open(QIODevice::ReadWrite)) { connect(serial, QSerialPort::readyRead, this, Widget::onReadyRead); } } void Widget::onReadyRead() { QByteArray data serial-readAll(); // 处理收到的数据注意分包问题 m_buffer.append(data); while (m_buffer.size() 4) { // 按自己的协议解析一帧 } }这里我要强调一个示例源码里不会写明、但实战非常重要的细节QSerialPort的readyRead信号并不保证一次收到完整一帧数据尤其在9600波特率下一帧数据可能被拆成好几段到达。所以我一直在串口模块里维护一个接收缓冲区先攒数据再按帧解析而不是收到一次就处理一次。另外如果你在Linux下开发串口程序注意串口设备名通常是/dev/ttyUSB0或/dev/ttyS0需要当前用户有访问权限否则open会失败。这一点在Windows上不明显交叉到嵌入式环境时非常容易踩。4.3 二维码生成让实例代码在业务里落地QT5生成二维码也是高频搜索词。源码里通常有两个实现方向用现成的QZXing库做编码解码或者调用纯C库qrencode生成二维码图片再用Qt的QPixmap显示。我推荐在Windows桌面工具里用qrencode的方式因为依赖小生成步骤透明出了问题容易排查。大致流程如下#include qrencode.h QImage generateQrCode(const QString text, int size) { QRcode *qr QRcode_encodeString(text.toUtf8().constData(), 0, QR_ECLEVEL_M, QR_MODE_8, 1); if (!qr) return QImage(); QImage img(qr-width 8, qr-width 8, QImage::Format_Mono); img.fill(0xffffffff); for (int y 0; y qr-width; y) { for (int x 0; x qr-width; x) { if (qr-data[y * qr-width x] 1) { img.setPixel(x 4, y 4, 0xff000000); } } } QRcode_free(qr); return img.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); }这段代码的关键点在于QRcode_encodeString的返回值生命周期由我们自己管理用完必须QRcode_free否则就是内存泄漏。我在源码里看到不少示例漏掉了这一步短时间运行没问题工具长时间挂在后台就会慢慢吃掉几十MB内存。二维码内容如果是中英文混合务必转成UTF-8编码再传给编码函数否则生成的码扫码出来是一堆乱码。这是我在工单系统里踩过的坑。4.4 多线程与任务队列别让界面卡死一个QT5界面程序如果处理大文件时界面卡死十有八九是主线程里干了太多重活。源码里多线程示例通常有标准解法。比较推荐的方式是QtConcurrent执行任务简单直接#include QtConcurrent/QtConcurrent QFutureQString future QtConcurrent::run([this]() { // 耗时操作比如读取大文件 QString result doHeavyWork(); return result; }); auto watcher new QFutureWatcherQString(this); connect(watcher, QFutureWatcherQString::finished, this, [this, watcher]() { QString result watcher-result(); // 回到主线程更新UI ui-label-setText(result); watcher-deleteLater(); }); watcher-setFuture(future);这里最关键的是不要在工作线程里直接操作UI控件。Qt要求所有对控件的操作都在主线程的事件循环里完成QtConcurrent::run跑完线程后通过QFutureWatcher的finished信号回到主线程再由lambda更新界面这就是一个标准且安全的闭环。如果你更喜欢子类化QThread那要注意run()方法结束后线程并不自动销毁需要在finished信号里deleteLater。示例代码里如果没有写这一步你自己维护时间久了就会发现内存一直涨。4.5 数据库与模型视图源码里隐藏的高频套路最后说一下数据库。很多桌面工具都带本地存储需求源码里的数据库示例一般从QSqlDatabase的创建开始再到增删改查最后是模型视图。我经常用到的组合是QSqlTableModel加QTableView几十行代码就能实现一个可编辑的历史记录表QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(data.db); if (!db.open()) { qWarning() database open failed; return; } QSqlTableModel *model new QSqlTableModel(this, db); model-setTable(record); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); ui-tableView-setModel(model);这个套路的优点是省掉了自己维护QTableWidgetItem的麻烦数据变化时模型会自动刷新视图。但需要记住默认的OnManualSubmit策略下用户改了表格后要手动调用model-submitAll()才会写库否则只是内存里的变化。很多新手以为表格里能编辑就是写进去了结果是关程序之后数据全没了。如果要做外键关联或者复杂查询直接执行QSqlQuery也行但模型视图这套适合90%的简单台账需求示例源码里这种写法非常值得直接拿走。5. 这几个高频坑我建议你直接收藏读源码、跑示例过程中遇到的各种坑我挑高频的集中说一下。5.1 中文乱码与编码问题在QT5里中文乱码几乎是最常见的投诉。我总结下来主要是三类问题源文件编码不对。Windows下如果不是UTF-8带中文的字符串会乱码。统一文件编码为UTF-8能解决大部分问题。字符串常量没经过QString::fromUtf8或中文文本用tr()包裹不当。在源码里看到类似QStringLiteral(中文)的写法好处是编译期处理运行效率高我喜欢这种方式。MSVC和MinGW对UTF-8源文件的支持不同MSVC老版本可能还需要在文件头部加一定程度的编译选项。如果你用MSVC编译出现乱码先检查源文件编码再检查是否设置了utf-8相关的编译标志。5.2 高分屏下界面模糊和控件错位现在的笔记本普遍是2K、4K分辨率Qt5默认情况下可能出现界面模糊或控件挤成一团。解决办法是在main函数开头主动开启高分屏支持int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); // ... }注意Qt 5.14以后高分屏支持默认开启如果你还在用旧版本建议显式设置。示例源码里如果没有这行在高分屏机器上跑起来很虚别急着怀疑代码先检查这个属性。5.3 打包发布时缺DLL源码在开发机跑得好好的拷到别人电脑上却说缺少Qt5Core.dll、Qt5Widgets.dll这是典型的没做部署。Qt官方提供的windeployqt工具就是干这个的windeployqt your_app.exe这个工具会自动把需要的Qt模块DLL、平台插件复制到exe所在目录。但要注意如果你的程序用了串口模块需要确认SerialPort对应的DLL也被复制进去如果用了编译器运行库可能需要一并带上。我在发布串口工具时还遇到过平台插件目录plugins/platforms缺失导致程序双击毫无反应这些都是打包时容易漏掉的细节。5.4 Android与交叉编译注意别把环境问题当代码问题热词里同时有qt5配置android环境、arm qt5编译这两个方向。我在嵌入式板子上做过Qt界面最大的体会是交叉编译环境里的问题八成是工具链和库版本不匹配不是源码错误。配置Qt Android环境时核心是SDK、NDK、Java版本要和Qt版本对应。版本不匹配时编译报错非常奇怪可能是在某些头文件找不到stdint.h或者链接器报告缺少某个so。网上有针对具体版本的对照表建议对照着选不要每个都装最新版。ARM板上的交叉编译就更依赖板厂的工具链。一般流程是写一个交叉编译工具链的cmake配置文件指定CMAKE_C_COMPILER、CMAKE_CXX_COMPILER、CMAKE_SYSROOT然后让Qt自己找到目标平台的qmake或cmake前缀。只要你用的Qt源码和自己编译的库一致源码示例通常不用大改。但如果板子上的Qt版本和宿主机上不一致界面库的ABI会不兼容运行起来会直接段错误这也是环境问题。6. 源码复用的正确姿势与后续扩展思路最后聊点更实际的。当你把这套源码里几个模块跑通后怎么把它们变成自己的东西而不是永远停留在“照着敲一遍”的程度。6.1 抽离公共模块保留最小依赖我的习惯是每读一个示例就把它对应的核心类复制到一个公共目录然后尝试删掉所有和主窗口demo相关的代码只保留独立功能类。比如串口示例里我可以把串口配置、数据读取缓冲区抽成一个SerialWorker类它不依赖界面任何控件这样以后任何项目都能用。这个抽离过程会逼你理解哪些类成员是功能必需的哪些只是为了demo演示。如果发现删掉某个成员会导致编译失败这正好是一个深入学习的机会。6.2 用示例代码做一次“二次开发”在抽离公共模块后做一个稍微超出原示例的小功能。比如源码里有二维码生成那我就加一个批量生成功能读取Excel或文本文件把每一行内容生成一个二维码图片文件同时输出日志。这个功能听起来不难但会涉及文件对话框、循环处理、进度条更新、错误处理等多方面的串联做完之后你对Qt的整体认知会提升一大截。6.3 往现代开发方向扩展QT5框架虽然经典但它的API完全可以封装到现代应用里。我自己就用这套源码里的串口和图表模块给一个数据采集项目写过界面后来又在网络请求模块的基础上把结果上报到后端的HTTP接口。思路都是同一个让Qt做它最擅长的桌面交互和本地逻辑复杂运算交给独立线程对外通信走标准网络协议。如果你对更热门的agent开发、智能体方向感兴趣也可以用Qt做客户端壳把大模型API的调用封装在独立的Network模块里界面负责对话展示。源码里网络请求和JSON解析的示例就是干这个的基础。6.4 我的个人体会我做过很多语言的界面程序兜兜转转最后还是离不开Qt。原因很简单Qt的开源生态、跨平台能力、信号槽机制让桌面工具的开发效率非常高。而这套QT5开发及实例配套源代码无论你是把它当成入门练习还是当成本地工具库的参考手册只要能坚持读完三五个模块亲手改一改跑一跑收获会远远超过“看一遍”。最后再分享一个我自己的小习惯源码里每个示例跑通后我都会在文件头写一段注释记下当时运行环境的Qt版本、编译器和遇到的一个关键问题。别小看这几行字半年后你回头翻源码这几行字比任何教程都管用。本文还有配套的精品资源点击获取
返回列表