在Qt中集成QXlsx:静态库编译、单元格读写与样式图表全攻略
2026/9/10 2:26:46 网站建设 项目流程

简介:QXlsx-master 是面向 Qt 开发者的 Excel 读写解决方案源码包,基于 C++ 实现,封装了 XLSX 文件的创建、解析与编辑 API,可完成报表导出、数据导入、单元格样式设置和图表生成等任务;源码已按 QtCreator 工程整理,适合需要在桌面应用中集成 Excel 处理能力的中高级 C++ 开发者。压缩包内共 753 个文件,包括 480 个示例测试用 xlsx、72 个 cpp 源文件、54 个 h 头文件,以及 12 个 pro 工程文件、静态库与说明文档等,整体约 14.93MB,结构清晰便于直接引用和二次编译。目前已有 900 人学习,是一份小而完整的库源码。通过研读源码和示例,开发者能快速掌握 QXlsx 的文档对象模型、单元格读写、样式格式设置及工作表管理等方法,也可直接链接附带的静态库,节省自行实现 Excel 功能的时间,高效完成 Qt 程序中的数据交互与导出需求。

1. 别把 QXlsx 当成临时脚本工具,它其实是 Qt 工程里最像样的 xlsx 后端

拿到 QXlsx-master 这套源码时,很多人以为它是又一个写死模板的导出 demo,但源码包里 xlsxdocument.cpp、xlsxworksheet.cpp、xlsxstyles.cpp、xlsxchart.cpp 加上 fort.c 和 run.cmd 放在一起,本身就是一条完整的 XLSX 编译链路,最终产物是 libQXlsx.a 这种能被 QtCreator 直接链接的静态库。它不依赖 Office COM 组件,在 Windows、macOS、Linux 上都能编译,所以特别适合 C++ 客户端程序做批量报表导出、读取财务回执或维护模板文件。下面这篇会从 QtCreator 里的工程组织方式讲起,逐步拆解如何把 QXlsx 编译成库、写入单元格、叠加样式和条件格式,再把链接失败时的排错经验收个尾。

2. 在 QtCreator 里把 QXlsx-master 编译成静态库

2.1 先看清源码包里哪些文件必须进工程

解压 QXlsx-master 之后,头文件都在根目录下,所有以xlsx开头的 .cpp 源文件就是库本体。.a文件是某个环境预编译出来的产物,不建议直接拿来用,因为编译器版本和 Qt 的套件位数很可能对不上,自己重新编译才靠谱。

新建一个 Qt 库工程时,要点是把这些文件都加进 SOURCES:xlsxdocument.cppxlsxworksheet.cppxlsxstyles.cppxlsxchart.cppxlsxdrawinganchor.cppxlsxformat.cppxlsxconditionalformatting.cpp。还有让人容易忽略的fort.c,它负责兼容某些编译器在解析浮点数字时的异常行为,如果漏掉这个 C 文件,编译时会出现大量与数字解析相关的未定义符号。run.cmd是 Windows 下的快速构建脚本,内部就是依次调用 qmake 和 make,在不打开 QtCreator 图形界面的情况下也能出库。

2.2 .pro 构建文件的核心参数

用 QtCreator 新建项目时选择"Library > C++ Library",然后在 .pro 里做最小化配置。我的建议是不套额外的子目录工程,直接让 QXlsx 源码目录自身就是一个静态库工程,这样 QtCreator 打开源码包根目录的 .pro 就能一键构建。

QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = lib CONFIG += staticlib c++17 TARGET = QXlsx DEFINES += QXLSX_STATIC INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$files($$PWD/*.h, true) SOURCES += $$files($$PWD/*.cpp, true) SOURCES += $$PWD/fort.c

TEMPLATE = lib告诉 qmake 生成库文件而不是可执行程序,CONFIG += staticlib直接决定产物后缀是.a(MinGW 和 Linux 下)而不是.dllQXLSX_STATIC这个宏必须在编译库和使用库的两端同时定义,否则头文件里会按动态导出方式处理符号,导致链接阶段找不到实现。$$files($$PWD/*.h, true)的第二个参数true表示递归扫描子目录,如果源码包里额外带了一个 include 子目录,这个写法能把里面的头文件一并收进来,省去手动逐个添加的麻烦。

2.3 命令行编译与 run.cmd 的实际行为

在 QtCreator 里点击构建之后,左下角的编译输出会带出完整的命令。如果你更习惯命令行,可以手动执行:

cd /path/to/QXlsx-master qmake QXlsx.pro -spec win32-g++ "CONFIG+=release" mingw32-make -j8

-spec win32-g++是让 qmake 使用 MinGW 工具链,接下来的mingw32-make -j8中的-j8指定 8 个并行任务,能明显缩短编译时间。整个过程里 run.cmd 做的事就是这样两步,只是额外做了一层目录切换和错误码检查,编译失败时能直接返回非零退出码,方便 CI 脚本感知。

2.4 在自己的业务工程里正确链接 libQXlsx.a

库编译成功后会生成类似libQXlsx.a的文件。业务工程里不要把这个 .a 文件直接拖进项目,正确的做法是在 .pro 中用 LIBS 指定路径:

INCLUDEPATH += $$PWD/../QXlsx-master LIBS += -L$$PWD/../QXlsx-master -lQXlsx DEFINES += QXLSX_STATIC

-L后面是库文件所在目录,-lQXlsx让链接器自动寻找名为QXlsx的库文件,MinGW 会拼接成libQXlsx.a。这里有个容易被忽略的参数顺序问题:LIBS必须出现在源文件列表之后,GNU 链接器对静态库的扫描是单遍的,如果库放在对象文件前面,符号未满足时会直接跳过,表现就是明明库路径对了还会报一堆undefined reference

编译期常见错误对照表:

错误现象原因处理方式
找不到QXlsx::Document头文件路径没配上检查 INCLUDEPATH 是否指向源码根目录
大量undefined reference业务工程没有定义 QXLSX_STATIC在业务 .pro 和库 .pro 同时加 DEFINES
提示重复定义把 .cpp 源文件直接加入了业务工程只保留源码工程的 SOURCES,业务工程用链接方式
链接时找不到 -lQXlsx库名或目录拼错nm libQXlsx.a | grep QXlsx验证生成文件

3. 单元格读写:Document 和 Worksheet 的配合方式

3.1 新建、加载和保存 xlsx 的三种入口

QXlsx::Document对外的接口非常收敛,新建文件、加载已有文件和保存文件都集中在它身上。最常见的用法是直接构造一个空文档,然后往里面写单元格,最后saveAs()输出。

#include "xlsxdocument.h" #include <QDebug> QXlsx::Document doc; doc.setCellText(0, 0, "订单号"); doc.setCellValue(0, 1, 1024); doc.setCellFormula(0, 2, "SUM(B2:B100)"); doc.saveAs("order_summary.xlsx");

第 1 行的setCellText写入的是字符串类型,不管内容是不是纯数字,Excel 打开后都按文本处理,左上角会有绿色小三角提示。第 2 行的setCellValue接收QVariant,当传入整数时,它会区分 Excel 单元格里的数值类型,避免出现文本数字导致后续 SUM 统计不到的情况。第 3 行的setCellFormula写公式,QXlsx 不会去计算公式结果,它只是把公式表达式序列化进sheet1.xml,真正算出来要等 Excel 或 LibO 打开文件时触发重算。

3.2 写文本、数值和公式时的类型边界

写单元格时 row 和 col 都是从 0 开始计数的,(0, 0) 对应 Excel 里的 A1。很多人按 Excel 表格里看到的行列号直接传进来,结果数据整体错位一格。

QXlsx::Document doc; QXlsx::Worksheet *ws = doc.currentWorksheet(); ws->write(1, 1, QString("2024-12-01")); ws->write(1, 2, QDate(2024, 12, 1)); ws->write(1, 3, 3.14159); ws->write(1, 4, QVariant(true)); doc.saveAs("types.xlsx");

write()是比setCellText()更推荐的方式,因为它会对 QVariant 的实际类型做一次分发:QString 走文本通道,QDate 会被转为 Excel 日期序列号并在单元格上附加日期格式,double 写入数值节点,bool 则对应 Excel 的TRUE/FALSE。这样处理的好处是读取方用cell->value()取数据时,类型不会因为序列化而漂移。要注意write(1, 1, ...)的第二个参数是列,不是行,整个 QXlsx 的 API 都遵循 row、col 这个顺序,别和 Excel 的 A1 表示搞混。

3.3 读取已有文件并按行遍历数据

读取场景通常比写入更麻烦,因为你不知道上游文件的格式是否规整。QXlsx 推荐的做法是先加载,再取工作表,最后逐格判断 Cell 类型。

QXlsx::Document doc("input.xlsx"); if (!doc.load()) { qWarning() << "load failed"; return -1; } QXlsx::Worksheet *ws = doc.currentWorksheet(); for (int row = 0; row < 50; ++row) { QXlsx::Cell *cell = ws->cellAt(row, 0); if (!cell || cell->value().isNull()) { break; } qDebug() << row << cell->value().toString() << cell->cellType(); }

cellAt(row, 0)返回指向单元格对象的指针,空格返回nullptr,所以必须先判空再访问。cell->cellType()返回的枚举能区分是字符串还是数值类型,这个信息在做数据校验时很有用。还有一个细节是doc.load()的返回值,QXlsx 的 Document 构造函数即使文件不存在也不会抛异常,真正去解析文件的动作发生在load()里,检查它的返回值是判断文件是否损坏的第一道防线。

4. 样式、条件格式与图表锚点的工程实现

4.1 用 Format 控制字体、边框和对齐

xlsxformat.cpp里实现的QXlsx::Format类相当于 Excel 单元格的完整样式对象,字体、字号、颜色、边框、对齐、填充全都能设。每次设置单元格样式前都手动组装 Format 是可行的,但大量单元格共用同一种样式时,应该提炼出一个静态 Format 实例反复使用,否则文件体积会暴涨。

QXlsx::Format headerFmt; headerFmt.setFontSize(11); headerFmt.setFontColor(QColor("#333333")); headerFmt.setFontBold(true); headerFmt.setBorderStyle(QXlsx::Format::BorderThin); headerFmt.setTextWarp(true); headerFmt.setHorizontalAlignment(QXlsx::Format::AlignHCenter); QXlsx::Document doc; doc.write(0, 0, QStringLiteral("总计"), headerFmt); doc.write(0, 1, 8888, headerFmt); doc.saveAs("styled.xlsx");

setBorderStyle只影响该格式对象持有的边框类型,如果只设了外部边框而没设内部边框,表格区域内的单元格还是光秃秃的。另外QXlsx::Format::AlignHCenterQt::AlignHCenter是两个不同的枚举,前者是 QXlsx 自己的,用于序列化进 XML,后者是 Qt 的布局枚举,混用的话编译能过,但样式落不进文件。

4.2 条件格式与工作表管理

条件格式由xlsxconditionalformatting.cpp里的ConditionalFormatting类负责。它可以给一段连续区域添加规则,比如让超标数据自动变红,这比在代码里逐个单元格判断数值再改颜色要高效得多。

QXlsx::Document doc; doc.addSheet("月度明细"); doc.setSheetName(0, "汇总"); QXlsx::Worksheet *ws = doc.worksheet("月度明细"); QXlsx::ConditionalFormatting cf; QXlsx::Format warnFmt; warnFmt.setPatternBackgroundColor(QColor("#FFE0E0")); warnFmt.setFontColor(QColor("#B00000")); cf.addHighlightCellsRule( QXlsx::ConditionalFormatting::GreaterThan, "5", warnFmt); ws->addConditionalFormatting(QXlsx::CellRange("B2:B100"), cf); doc.saveAs("report.xlsx");

addHighlightCellsRule的第一个参数是规则类型,GreaterThan表示数值大于阈值时触发,第二个参数是阈值表达式,这里传的"5"最终会写进 XLSX 的conditionalFormatting节点里。addConditionalFormatting接收CellRange,字符串"B2:B100"会被解析为行 1~99、列 1 的区域,注意这里的行列起点是 1,不是代码 API 里常见的 0。工作表的管理上,addSheet在末尾追加,setSheetName按索引改名,要覆盖同名工作表之前不会自动删旧表,得先用deleteSheet()清掉。

4.3 图表、DrawingAnchor 和图片的位置细节

图表是xlsxchart.cpp的管辖范围,底层的定位机制则在xlsxdrawinganchor.cpp里。QXlsx 里插入图表并不复杂,难点在于图表数据区域的引用是字符串写入 XML 的,如果数据源还没写进工作表,图表保存后打开会是一片空白。

QXlsx::Document doc; QXlsx::Worksheet *ws = doc.currentWorksheet(); for (int i = 0; i < 10; ++i) { ws->write(i, 0, i + 1); ws->write(i, 1, (i + 1) * (i + 1)); } QXlsx::Chart *chart = doc.insertChart(2, 4, QSize(360, 240)); chart->setChartType(QXlsx::Chart::CT_BarChart); chart->addSeries(QXlsx::CellRange("A1:A10"), ws); chart->addSeries(QXlsx::CellRange("B1:B10"), ws); doc.saveAs("chart_demo.xlsx");

insertChart里的 (2, 4) 表示图表左上角锚定在 C2 单元格附近,QSize控制图表绘制区域的大小。addSeries的第一个参数是数据区域,第二个参数必须传数据所在的工作表指针。这个过程里drawinganchor.cpp负责把图表锚点转换成 XLSX 内部的两套坐标体系:一个是搜索 "anchor" 用的绝对定位,一个是跟随单元格变化的相对偏移;如果锚点坐标传错,常见的症状是图表出现在表格区域之外,数据却没有任何问题。

常用图表类型对照:

枚举值对应图表适用场景
CT_BarChart柱状图分类对比
CT_LineChart折线图趋势展示
CT_PieChart饼图占比拆解
CT_AreaChart面积图堆积量变化
CT_ScatterChart散点图相关性分析

5. 静态库链接失败排查与体积瘦身思路

5.1 链接错误对照表

用 QtCreator 编译 QXlsx 类库本身很少出问题,真正的坑都集中在业务工程链接它的时候。

错误信息段原因修复方向
undefined reference to QXlsx::Document::saveAs库文件没有参与链接确认LIBS += -L... -lQXlsx拼写
multiple definition of ...fort...fort.c 被误加到业务工程从业务工程移除 fort.c,只保留库内的
cannot find -lQXlsx库文件路径错误检查 .a 文件所在目录与 -L 参数是否一致
头文件红色找不到QtCreator 的代码模型没刷新右键项目执行 Run qmake

.a文件的链接与对象文件的顺序紧密相关。把.pro文件里的LIBS放在SOURCES前面,在老的 MinGW 环境下几乎必现undefined reference,新版本虽然容忍度好一些,但最好还是遵循先编译对象、再扫静态库的顺序。

5.2 用 nm 和 objdump 检查 libQXlsx.a 的导出符号

当链接错误指向某个具体函数时,最优的验证手段是直接看库里的符号表,而不是反复猜测路径。

nm libQXlsx.a | grep "Document" objdump -t libQXlsx.a | grep "saveAs"

nm输出的第二列是符号类型,T表示这是文本段里的全局函数符号,如果看到的是U说明该符号在这个库中是未定义的,它需要依赖其他库提供。objdump -t的过滤器更能直观确认某个具体函数是否编译进了最终产物。这个验证手法在 C 和 C++ 混合的项目里尤其重要,因为 C++ 符号经过 name mangling 之后,直接用函数名 grep 不到时,要学会用c++filt还原:

nm libQXlsx.a | grep "saveAs" | c++filt

5.3 发布时的静态裁剪与编译选项

生产环境里如果要把 QXlsx 静态库打进最终可执行文件,建议开启QT += core gui之外的 release 构建,并在 .pro 里追加体积控制参数:

CONFIG += release QMAKE_CXXFLAGS_RELEASE += -Os -fno-exceptions QMAKE_LFLAGS_RELEASE += -s

-Os让编译器按体积优化,-fno-exceptions显式禁止异常展开代码段,-s会在链接时剥离符号表。加了这三个参数后,libQXlsx.a 的体积通常能压缩约 20%,代价是异常捕获能力被关掉,适合那些不需要在 Excel 处理层做 try/catch 的稳定场景。如果还想继续缩小,可以在业务工程里只保留实际用到的源文件重构一个最小库,但要同步保留 xlsxdocument.cpp 和 xlsxworksheet.cpp 这对核心组合,否则单元格读写和序列化链路会断掉。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询