news 2026/10/22 9:41:23

Qt 5.15.2 + VS2019 环境下 QtXlsx 集成与 Excel 读写实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt 5.15.2 + VS2019 环境下 QtXlsx 集成与 Excel 读写实战指南

最近给一个Windows桌面工具加报表导出功能,数据量不大但格式要求不少:表头要加粗、要有合并单元格、多张工作表、还得支持读取别人填好的Excel再回写。开发环境是Qt 5.15.2加VS2019,网上翻了半天,可用方案无非是COM调用Office、转CSV、手写XML这几条路,挨个试下来都不太顺,最后定在用QtXlsx。这篇文章把从拉源码、编译库到在VS2019工程里读写xlsx的完整过程记录下来,重点说说配置环节容易踩的坑,给准备在这套环境下用QtXlsx的同学一个可直接对照的参考。

1. 方案选型:为什么最终选了QtXlsx

1.1 台面上能用的几个替代方案

很多人的第一反应是用QAxObject去调COM接口操作Excel。这个方案最大的优点是Office亲自下场,什么样式都能做,但代价也很明显:客户端必须装Office,每次操作都要拉起一个COM实例,几百个单元格写进去要来回调用多次,速度起不来。更麻烦的是部署,稍微要点安全权限的机器上,DCOM配置就能让你调试一整晚。我这边是要给同事的办公电脑分发工具的,不可能要求每个人都装Office再开宏权限,所以COM方案第一个被排除。

第二个常见方案是导出CSV。CSV确实简单,写个循环把数据拼成字符串存成txt就行。但问题在Excel打开CSV时对编码很敏感,UTF-8无BOM的中文内容经常乱码;而且CSV不支持多工作表、不支持合并单元格、不支持公式,也没有真正的列宽样式。如果只是导出最简单的一维表,CSV可以顶一下,一旦涉及"要好看一点的报表",它就无能为力了。

第三个路子是手写OOXML。xlsx文件本质是一个ZIP包,里面按固定目录结构放着一堆XML:[Content_Types].xml、_rels/.rels、xl/workbook.xml、xl/worksheets/sheet1.xml等等。理论上你可以用QZipWriter把这些XML拼出来。但OOXML的关系结构非常绕,少写一个relationship、命名空间拼错一个前缀,Excel打开就会弹"文件损坏,是否修复"。我自己试过做一个最简单的三行表,调试那个package关系就耗了一天,维护成本太高,不适合作为正式功能。

QtXlsx走的是另一条路:它把这些XML生成逻辑全部封装成QXlsx::Document这样的对象模型,你只管对着单元格写数据、设格式,剩下的文件打包细节库内部处理。它不依赖Office安装,不依赖Python运行时,是一个纯C++的静态/动态库,放进Qt工程就能用,正好契合"桌面端离线生成Excel"这个核心诉求。

1.2 QtXlsx的原理与能力边界

理解QtXlsx的工作方式,对后面排查问题很有帮助。它把xlsx文件当作一个普通的ZIP包处理,写入时在内存里构建xl/worksheets/sheet*.xml、xl/sharedStrings.xml等XML文档,再用Qt自带的zip能力压缩打包;读取时则做逆过程,解包、解析XML、把单元格内容还原成QVariant。所以它的运行不依赖Excel进程,这也是它能做轻量部署的底层原因。

能力边界要提前说清楚。QtXlsx支持的是OOXML规范里的常用部分:单元格读写、字符串和数字类型、日期时间、公式的写入和缓存值读取、字体加粗/颜色/对齐、背景填充、边框、合并单元格、行高列宽、冻结窗格、多工作表。它不支持图表、图片嵌入,也不支持读取条件格式和数据透视表这类高级对象。另外它只支持.xlsx,不支持老的.xls二进制格式。如果你需要的功能超出了这个范围,可以考虑社区维护度更高的QXlsx分支,但API可能和原版有些出入,具体以你拉到的源码头文件为准。

2. 编译前的环境准备:组件、版本与Perl依赖

2.1 组件清单与版本对应关系

我这边最终使用的环境组合如下,建议照着这个版本对应关系来,不要混搭:

组件版本备注
Qt5.15.2通过在线安装器安装,选择msvc2019_64组件
编译器VS2019 MSVC v142工具集务必和Qt组件对应的编译器一致
IDEVisual Studio Community 2019安装"使用C++的桌面开发"工作负载
Qt VS Tools最新版扩展里把Qt版本路径配到msvc2019_64目录
Perl任意Windows发行版构建Qt模块时需要,后面细说
qmake/nmake随Qt安装自带开始菜单有对应的命令行快捷方式

安装Qt时有一个常见误区:看到"MinGW 64-bit"就顺手勾了,结果用VS2019去建工程,编译QtXlsx时一堆链接错误。Qt 5.15的组件里,msvc2019_64是给MSVC编译器用的,MinGW是另一套工具链。如果你用VS2019写代码,组件一定要选msvc2019_64,否则后面静态库和动态库的符号约定对不上。

2.2 Perl这个不起眼的隐藏依赖

第一次在命令行跑qmake编译QtXlsx时报了个错,信息大致是找不到Perl,我当时第一反应是"一个C++项目要Perl干嘛"。后来才明白,Qt的模块化构建体系里有个叫syncqt的脚本,用于同步模块的头文件结构,它是用Perl写的。QtXlsx以Qt模块的标准形态组织源码,qmake解析工程时会触发这个脚本,所以构建环境里必须有一个可被命令行找到的Perl。

解决方法是装一个Windows版的Perl发行版,常见的是Strawberry Perl,装完后把它的安装目录加进系统PATH。注意改完PATH要重新打开命令行窗口,环境变量不会自动刷新到已开的窗口。另外,如果你之前装过其它Perl发行版,在命令行里执行perl -v确认一下版本,避免PATH里有多个Perl互相干扰。

2.3 打开一个干净的构建命令行

编译QtXlsx不需要打开VS2019图形界面,直接在命令行里做反而干净。关键是让命令行同时拿到两套环境:VS2019的编译环境变量和Qt的bin目录。

我习惯这样操作:打开"x64 Native Tools Command Prompt for VS2019",然后手动把Qt的bin目录追加到PATH里:

set PATH=C:\Qt\Qt5.15.2\5.15.2\msvc2019_64\bin;%PATH%

也可以用Qt安装器生成的"Qt 5.15.2 (MSVC 2019 64-bit)"快捷方式,那个已经把QTDIR设置好了,但为了保险,我还是建议在VS2019的原生命令行里把Qt路径手动加一遍。检查环境是否就绪,执行:

qmake -v cl nmake /?

三条命令都不报"不是内部或外部命令"就算OK。

3. 从源码构建QtXlsx的完整过程

3.1 获取源码与分支选择

在代码托管平台搜索QtXlsx,能看到原版仓库和一个长期维护的QXlsx分支。考虑到当前用的是Qt 5.15,原版就能正常编过,我直接拉的原版master分支。拉代码时注意不要用发布页的zip包和git clone混着来,选一种方式即可,clone我记得在Windows上更省事:

cd /d D:\Libs git clone https://host/QtXlsx.git cd QtXlsx

如果网络不方便clone,下zip包解压到D:\Libs\QtXlsx也是一样的。源码目录结构里,核心代码在src/xlsx下,顶层有个QtXlsx.pro,这个pro就是整个工程的构建入口。README里如果写了构建说明,建议先瞄一眼,不同tag的构建方式略有差异。

3.2 qmake和nmake实践记录

构建分两步,先qmake生成Makefile,再nmake编译。在源码根目录执行:

qmake QtXlsx.pro nmake

如果Qt安装目录的bin下有jom.exe,可以把nmake换成jom并行构建,速度会快不少:

jom -j8

这里有几个细节经验。第一,建议在干净的源码目录里直接qmake,不要用shadow build,因为后面要用.pri文件时,源码路径和生成路径隔太远反而麻烦。第二,如果顶层pro把examples一起纳入构建导致报错,可以只进src目录构建核心库:

cd src qmake src.pro nmake

第三,Debug和Release要分别编一次。先默认编Release,再把构建方式切到debug编一遍,后面集成时就不会出现Debug工程链Release库的问题。切换方式是在命令行里给qmake传配置参数:

qmake CONFIG+=debug .. nmake

看到生成了Qt5Xlsx.dll、Qt5Xlsx.lib这类文件,编译就算通过了。

3.3 验证构建产物

构建完成后,去src/xlsx目录或者你在qmake时指定的输出目录看一眼,常常能看到这些文件:动态库文件、导入库文件、以及一堆头文件。以我的目录规划为例,我会把库文件和头文件单独整理出来,方便多个工程共用:

D:\Libs\QtXlsx\include\ (*.h,从src\xlsx拷过来) D:\Libs\QtXlsx\lib\ (Qt5Xlsx.dll、Qt5Xlsx.lib)

注意Debug版库名可能带d后缀,比如Qt5Xlsxd.dll,这是Qt的命名习惯,后面配置工程时要把Debug和Release的库名区分开。整理完后可以用dumpbin /dependents看一下动态库依赖了哪些Qt库,如果提示找不到某个Qt DLL,先把Qt的bin目录加到PATH再执行。

4. 在VS2019工程里集成QtXlsx

4.1 方式一:qtxlsx.pri源码直引

集成方式有两条路,第一条是用QtXlsx自带的.pri文件把源码直接纳入你的工程。.pri文件是qmake的"子项目文件",里面已经写好了需要编译的源文件列表和头文件路径。在你的.pro工程里加一行include,编译时这些源码会跟着你的工程一起编进exe里。

假设我的工程pro文件在E:\Code\Reporter\Reporter.pro,QtXlsx源码在D:\Libs\QtXlsx,那么在pro里加:

include(D:/Libs/QtXlsx/src/xlsx/qtxlsx.pri)

这里路径建议用绝对路径或者基于$$PWD的相对路径:

include($$PWD/../3rdparty/QtXlsx/src/xlsx/qtxlsx.pri)

用$$PWD的好处是工程换目录后qmake自动重新解析,不用手改路径。注意路径里的斜杠统一用/,反斜杠在pro里有时会出问题。

这种方式优点是配置最简单,不需要关心库文件的Debug/Release配对问题,因为它根本没走链接库那条路,所有源码直接打在exe里。缺点是每次全量编译会多编一批xlsx相关的cpp文件,首次编译时间长一点。我个人做小工具时优先用这种方式。

4.2 方式二:链接编译好的Qt5Xlsx库

如果工程已经比较大,或者多个工程要共用同一个QtXlsx库,建议用第二种方式:链接我们之前编译好的库文件。在pro里配置:

INCLUDEPATH += D:/Libs/QtXlsx/include CONFIG(debug, debug|release) { LIBS += -LD:/Libs/QtXlsx/lib -lQt5Xlsxd } else { LIBS += -LD:/Libs/QtXlsx/lib -lQt5Xlsx }

-L指定库搜索目录,-l指定库名。qmake在MSVC下会把-lQt5Xlsx自动解析成Qt5Xlsx.lib,Debug版的d后缀也是在CONFIG判断里手动加的,这一步一定要和实际构建出的库文件名对应上,否则链接时找不到库。

如果你用的是CMake而非qmake,那么配置思路一样,只是写法不同:include_directories指向头文件目录,target_link_libraries传入完整路径的.lib文件。VS2019配合Qt VS Tools的话,qmake工程和原生VS工程都能用Qt VS Tools建,但前提是在扩展设置里把Qt版本正确指到C:\Qt\Qt5.15.2\5.15.2\msvc2019_64,这一项配错会导致Qt头文件找不到。

4.3 DLL部署与MSVC编码开关

如果用动态库方式集成,运行时需要把Qt5Xlsx.dll放到exe同目录。方法是在VS工程属性里加一条后期生成事件:

copy /Y D:\Libs\QtXlsx\lib\Qt5Xlsx.dll "$(OutDir)"

用.pri源码直引方式的话,这一步可以跳过。但不管哪种方式,最终发布时都建议用Qt自带的windeployqt工具,把依赖的Qt基础DLL一起收集齐全,不然到了没装Qt的机器上,运行时大概率提示缺少Qt5Cored.dll或Qt5Core.dll。

另外在pro里给MSVC加一个编译选项,强烈建议:

msvc: QMAKE_CXXFLAGS += /utf-8

这个开关告诉编译器源码文件按UTF-8解释。QtXlsx的头文件和你的cpp里如果包含中文注释或中文字符串,不加这个选项,MSVC很容易报C4819警告,严重的会把中文字符串字面量解析成乱码写进Excel。加了之后中文存储问题基本一次解决。

5. 实战代码:读写Excel的核心实现

5.1 写入示例:生成带格式的报表

配置完成后,写一个最小demo验证环境。创建新工作簿,写几行数据,设置表头格式,合并标题行,最后保存文件:

#include <QGuiApplication> #include "xlsxdocument.h" #include "xlsxformat.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QXlsx::Document doc; // 标题格式 QXlsx::Format titleFormat; titleFormat.setFontSize(14); titleFormat.setFontBold(true); titleFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); // 表头格式 QXlsx::Format headerFormat; headerFormat.setFontBold(true); headerFormat.setPatternBackgroundColor(QColor(217, 225, 242)); headerFormat.setFillPattern(QXlsx::Format::PatternSolid); // 第一行合并作为标题 doc.mergeCells(QXlsx::CellRange("A1:D1")); doc.write(1, 1, "项目执行情况统计", titleFormat); // 第二行表头 doc.write(2, 1, "编号", headerFormat); doc.write(2, 2, "名称", headerFormat); doc.write(2, 3, "启动日期", headerFormat); doc.write(2, 4, "按期完成", headerFormat); // 数据 doc.write(3, 1, "PRJ-001"); doc.write(3, 2, "模拟项目X"); doc.write(3, 3, QDate(2025, 6, 1)); doc.write(3, 4, true); doc.write(4, 1, "PRJ-002"); doc.write(4, 3, QDate(2025, 7, 15)); doc.write(4, 4, false); // 列宽行高调整 doc.setColumnWidth(1, 16); doc.setColumnWidth(2, 30); doc.setRowHeight(1, 32); doc.setRowHeight(2, 24); if (doc.saveAs("report.xlsx")) { qDebug() << "保存成功"; } else { qDebug() << "保存失败"; } return 0; }

这里要注意mergeCells的参数是QXlsx::CellRange对象,直接传"A1:D1"这个字符串在某些版本上也能隐式转换,但我建议显式构造,减少编译报错的概率。write函数是按行号列号从1开始定位的,不是0开始,这个和数组思维不一样,我第一次写成了write(0, 0, ...)结果内容跑到了奇怪的位置。

5.2 读取示例:遍历工作表数据

读取已有工作簿用的是Document的另一个构造方式,然后通过dimension()拿到有数据的范围,再逐格读取:

#include "xlsxdocument.h" #include <QDebug> void dumpExcel(const QString &filePath) { QXlsx::Document doc(filePath); if (!doc.isLoaded()) { qWarning() << "加载失败:" << filePath; return; } QStringList sheetNames = doc.sheetNames(); qDebug() << "工作表:" << sheetNames; QXlsx::CellRange range = doc.dimension(); int lastRow = range.lastRow(); int lastCol = range.lastColumn(); for (int row = 1; row <= lastRow; ++row) { QStringList line; for (int col = 1; col <= lastCol; ++col) { QXlsx::Cell *cell = doc.cellAt(row, col); if (!cell) { line << QString(); continue; } QVariant value = cell->readValue(); line << value.toString(); } qDebug() << "行" << row << line.join("|"); } }

isLoaded()用来判断文件是否解析成功,如果传入的文件不是合法xlsx,这里会返回false,比直接崩溃友好得多。遍历时cellAt(row, col)返回null表示该单元格为空,这是很常见的判断。dimension()返回的是包含所有非空单元格的最小矩形,如果某个工作表只有右下角一个单元格有值,它的范围就是那个点,所以遍历前建议自己额外再确认一下范围是否符合预期。

5.3 格式化、公式与多表操作的细节

读写过程中,日期处理是大家问得最多的。写入时传QDateTime或QDate,QtXlsx内部会转成Excel的序列号存储,读取时用cellAt拿到的QVariant里保存的是QDateTime类型,可以直接toDateTime()转出来:

if (cell->cellType() == QXlsx::Cell::DateType) { QDateTime dt = value.toDateTime(); qDebug() << dt.toString("yyyy-MM-dd"); }

公式写入也很简单,write函数的value如果是字符串,被识别成公式的条件是字符串以=开头:

doc.write(5, 5, "=SUM(B3:B4)");

读取公式单元格时,readValue()拿到的是公式的缓存结果值,用cell->formula()才能拿到公式文本。要注意不是所有写入公式的场景都有缓存值,有些工具生成的xlsx里公式只有公式没有缓存,读出来的QVariant可能就是无效的。

多工作表操作,在同一个Document对象上加表、切换表、写数据:

doc.selectSheet("Sheet1"); doc.write(2, 2, "第一张表的内容"); doc.addSheet("汇总"); doc.selectSheet("汇总"); doc.write(1, 1, "合计"); doc.write(2, 1, "=Sheet1!B2");

切换表以后再写数据,写的目标是当前选中的那张表,所以保存前最好确认当前工作表是不是你想写的那张。我吃过一次亏:数据写完了发现全跑到活跃表里去了,另一张表还是空的。

6. 常见问题与排查思路实录

6.1 高频报错对照表

把这一路踩过的坑整理成表格,基本覆盖了这套环境下的典型问题:

现象直接原因处理办法
qmake报找不到Perl构建Qt模块需要syncqt脚本安装Perl发行版并重新打开命令行确认PATH
编译报LNK2019/LNK2001QtXlsx库编译用的编译器和当前工程不一致确保都用MSVC2019,不要和MinGW产物混用
运行提示找不到Qt5Xlsx.dll动态库未拷贝到exe目录添加后生成事件copy DLL到输出目录
中文字符串乱码或C4819MSVC按本地代码页解析源文件加/utf-8编译选项,源码保存为UTF-8 with BOM
Excel打开提示文件损坏XML格式异常或非法控制字符先用压缩工具解包检查sheet1.xml,定位特殊字符串
链接找不到Qt5Xlsxd.libDebug/Release库名不匹配按CONFIG宏分别指定带d和不带d的库名
读出的日期全是无效值单元格类型判断漏了DateType用cellType()分支处理后再toDateTime
只有部分单元格被读到行号列号从0开始写记住QtXlsx是1-based定位,修正循环起点

第一个报错最迷惑,因为它看起来和你的代码毫无关系。第二个报错排在第二位,因为很多人会忘记在"链接器-输入"里添加库文件名。后面几个是运行时和编译期交叉出现的问题,按表格顺序排查基本能覆盖。

6.2 三个值得记住的排错习惯

第一个习惯是"把xlsx当压缩包看"。遇到Excel打不开或用QtXlsx读不出来时,别急着改代码,先复制一份文件,改后缀为zip,用解压工具打开。xlsx的目录结构是否完整、xl/worksheets/sheet1.xml里的XML是否解析正常,一眼就能看出问题。有次我发现生成的文件里有个单元格字符串带了控制字符,Excel打开就报修复,就是靠这个办法定位到的。

第二个习惯是"库文件编译环境只信命令行"。VS2019的IDE界面里要确认当前用的是哪个Qt版本、哪个编译器工具集,很容易看错。但命令行下qmake -v和cl输出的信息是最直接的。库文件和主工程务必用同一套编译环境,这一步确认过,后面链接错误至少少一半。

第三个习惯是"先最小复现再上量"。用QtXlsx写到几千行时性能还可以接受,但如果你的程序每写一个单元格都调一次write后再saveAs,大文件会非常慢。正确做法是把所有数据先写入内存里的Document,最后统一保存一次;如果数据量特别大,按批次分文件保存,不要一个sheet堆到几十万行,Excel本身对这种超宽表也不友好。

最后再分享一个自己用得顺手的扩展点:QtXlsx不直接支持图片,但我经常需要在报表里放一个二维码或条形码。我的做法是用QPainter把二维码画到QPixmap,再把图片转成Base64字符串,在别的列里用文本方式存储,或者单独生成一个图片目录和xlsx配套分发。这个方法不优雅,但在不引入更多依赖的前提下解决了需求。如果你也遇到类似的"库能力不够但不想换方案"的情况,这种把文件拆开处理的思路可以临时顶一阵子,等需求复杂到一定量级,再考虑升级到功能更全的QXlsx分支也不迟。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 9:40:57

ISM宽带DOA估计原理与工程实践:子带划分、协方差对齐与非相干融合

简介&#xff1a;本资源是一份面向信号处理与无线通信领域初学者及进阶研究者的宽带DOA估计技术实践材料&#xff0c;聚焦于非相干信号子空间&#xff08;ISM&#xff09;方法在宽带源定位中的应用&#xff0c;解决传统窄带DOA算法在宽频带、相位失稳或强噪声场景下精度下降的问…

作者头像 李华
网站建设 2026/10/22 9:40:30

Java Swing+MySQL机房管理系统源码解析与实战部署

简介&#xff1a;这是一套面向Java开发者与高校计算机专业学习者的机房管理信息系统完整开发资源&#xff0c;适用于课程设计、毕业设计及中小型IT基础设施管理项目实践。资源包含可直接编译运行的Java源码&#xff08;30个.java文件&#xff09;、已编译类文件&#xff08;54个…

作者头像 李华
网站建设 2026/10/22 9:36:38

基于YOLO与SpringBoot的生菜生长周期智能监测系统实践

搞农业项目的人应该都有同感&#xff1a;生菜这玩意儿长得快&#xff0c;但恰恰是“长得快”让监测成了麻烦事。一个中型大棚几千株生菜&#xff0c;想知道整体处于什么生长阶段、哪片区域要浇水、哪片该采收&#xff0c;靠人蹲在地里一株一株看&#xff0c;不现实。而拿通用目…

作者头像 李华
网站建设 2026/10/22 9:36:34

Unity数学应用实战:向量、四元数与坐标变换的核心逻辑

1. 先聊清楚&#xff1a;Unity里的“数学”到底指什么 做Unity开发这些年&#xff0c;我有个一直想讲的话题&#xff0c;就是数学。很多人一听“数学应用”就下意识觉得是回到课堂、啃公式&#xff0c;其实恰恰相反——你在Unity里写下的每一行移动代码、旋转逻辑、跳跃判定&am…

作者头像 李华
网站建设 2026/10/22 9:36:20

Spring Boot小型超市商品管理系统:毕业设计完整实战解析

1. 从毕业设计选题到落地&#xff1a;为什么我推荐这个项目如果你是计算机相关专业的学生&#xff0c;大概率经历过这样的场景&#xff1a;选题表上列了一堆题目&#xff0c;要么是"基于XXX的XX系统"看着高大上&#xff0c;结果打开源码发现是个空壳&#xff1b;要么…

作者头像 李华
网站建设 2026/10/22 9:36:00

支持向量机SVM实战:从最大间隔原理到Scikit-learn参数调优与避坑指南

1. 从“画一条线”说起&#xff1a;支持向量机到底在解决什么问题很多人第一次接触支持向量机&#xff08;SVM&#xff09;&#xff0c;脑子里冒出来的第一个画面就是“画一条线把两类点分开”。这个直觉没错&#xff0c;但如果只停在这一步&#xff0c;后面遇到核函数、软间隔…

作者头像 李华