简介QtXlsxWriter-qt6.zip 是专为 Qt6 适配的 QtXlsx 开源库源码包面向需要在 Qt6/C 项目中读取和生成 Excel 文件的开发者支持创建 xlsx 文档、设置样式与写入数据适合报表生成和批量表格处理等中高级开发场景。它在原版 2020-03-19 代码基础上进行源码修改包括头文件引用、枚举与 API 兼容、构建配置调整等并在 Deepin20 的 Qt6 环境下编译通过解决了不少 Qt6 兼容性问题。压缩包共 187 个文件、仅 561KB主要包含 67 个 cpp 实现文件、47 个 h 头文件、44 个 pro 工程文件以及 qdoc 文档、图片素材等cpp 为功能实现代码h 为公开接口pro 为 qmake 工程配置很适合直接集成到 Qt6 工程中构建使用。已有 412 人学习/下载说明这套适配源码对同类需求者有一定参考价值同时也是一个了解 QtXlsx 源码结构和 Qt6 迁移思路的切入点。从内容看源码覆盖工作表、样式、格式、条件格式、工作簿、图表、绘图锚点等关键模块目录结构清晰拿到后可直接编译体验也可参考其中的修改思路省去自己排查 Qt6 接口变化的成本。1. QtXlsxWriter-qt6.zip 是什么Qt6 项目里写 Excel 的开源库值不值得放进你的工程QtXlsxWriter-qt6.zip 是一份以 zip 形式分发的 Qt6 版 Excel 写入库源码包。场景先摆出来老板要你把三千行设备数据导成带表头、带颜色、能直接发给客户的 Excel。用 CSV中文乱码、长文本截断用 QAxObject 调 OfficeWindows 独占还要求本机装了 Office。QtXlsxWriter 走的是另一条路不依赖 Office直接把数据按 xlsxOffice Open XML规范写成 .xlsx 文件Windows、Linux、macOS 一份代码通用。它适合正在用 Qt6 做桌面工具或报表服务的开发者。下面按「原理 → 编译 → 参数 → 避坑 → 验证」的顺序把这个库从解压到上线讲清楚。2. QtXlsxWriter 在 Qt6 下的工作原理从 xlsx 的 ZIP 结构到 write() 的写入链路2.1 把 xlsx 拆开看ZIP 容器里的 XML 和 sharedStrings 机制.xlsx 不是一个单文件而是一个 zip 容器和你在 linux 上压缩当前文件夹到 zip 是一样的道理只是扩展名不同。用 unzip -l 打开任意一张 Excel 表你会看到[Content_Types].xml、_rels/.rels、xl/workbook.xml、xl/worksheets/sheet1.xml、xl/styles.xml以及一个并不总是出现的xl/sharedStrings.xml。workbook.xml 描述有几张 sheet、每张 sheet 的 rId 引用是什么sheet1.xml 才是真正的表格数据按行列坐标存值styles.xml 存颜色、边框、对齐这些样式sharedStrings.xml 存所有重复出现的字符串。QtXlsxWriter 做的事就是把这个过程反过来先在内存里维护一张张 sheet 的数据结构保存时把数据、样式、共享字符串分别序列化成 XML最后用 zip 后端打包落盘。这里的 zip 后端在不同移植版里并不统一有的继续用 QtGui 里的私有 QZipReader/QZipWriter有的换成了 minizip。这个差异直接决定你的工程最后要不要链 Qt6::Gui所以拿到源码包后第一件事不是看 API而是先搞清楚它的 zip 后端是哪一套。很多编译期的黑匣子问题根源就在这里。sharedStrings 的机制值得一提Excel 会把字符串去重后放进 sharedStrings.xmlsheet 里只存索引。QtXlsxWriter 写字符串时会自动做这层处理所以你在代码里写两次「设备名称」文件里只会生成一份共享字符串。这也是为什么直接拿文本编辑器改 xlsx 里的字符串很容易把整张表改坏——你破坏了索引关系Excel 就认为文件结构不合法。2.2 Document、Worksheet、Cell 与 Format四类对象的分工接着看库的 API 对象。最高一层是 QXlsx::Document对应 Excel 里的整个工作簿new一个 Document 就相当于新建了一张空白 workbookDocument 内部按索引维护多个 Worksheetsheet默认只创建一张。write(row, col, value)是 Document 最常用的入口它做的不是直接往文件里写字节而是按照行列坐标路由到当前活动 sheet再把值包成 QVariant 存进对应坐标的 Cell。Cell 保存值和它的样式引用。Format 则是一份样式描述包含字体、字号、加粗、颜色、边框、对齐、数字格式等属性同一个 Format 对象可以被多个 Cell 共用不会互相污染。坐标要从 1 开始而不是 0。Excel 的行列习惯是 A1 对应 (1,1)QtXlsxWriter 保留了这个约定write(0, 0, x) 不会报错但写出的位置会偏离你预期的左上角。这是新手最容易犯的第一个错误很多「数据写到表里位置不对」的问题先检查坐标有没有从 0 开始数。Document 还提供 mergeCells、setColumnWidth、setRowHeight 这些针对 sheet 布局的操作。注意命名是「单元格区域」而不是「单元格」比如 mergeCells(A1:F1) 合并的是整个第一行区域。读操作用read(row, col)返回 QVariant如果你想拿样式再通过cellAt(row, col)拿 Cell*value() 取数值、format() 取样式。2.3 Qt6 版和 Qt5 版为什么不通用构建系统与运行时依赖的差别同样叫 QtXlsxWriterQt5 时代的库和 Qt6 移植版不能混用也不建议把 Qt5 版源码直接塞进 Qt6 工程硬编。原因有三个层面。第一是构建体系。Qt6 之后 CMake 成为官方主推Qt6 移植版普遍补了 CMakeLists.txt 和 QXlsxConfig.cmake老 Qt5 版很多还是 .pro / qmake 工程。qmake6 还能用但你会发现在 Qt6 里 kit 管理、部署工具全是围绕 CMake 转的硬用 qmake 要手动处理一堆 find_package。第二是 Qt 自带 API 的变化。Qt6 移除了 QRegExp老代码里凡是用了 QRegExp 的解析逻辑都得改成 QRegularExpressionQVariant 的一些隐式转换行为收紧QZipReader/QZipWriter 在 Qt6 里仍属于私有 API能否 include 取决于你的模块配置。这些变化导致 Qt5 那份源码直接编译会出现大片「未声明标识符」。第三是 ABI 和运行时。用 Qt 5.15 编出来的库链接到 Qt 6.7 的应用程序链接器会给你 undefined reference就算侥幸链接过QString 的 d 指针布局不同运行期照样崩。顺便说一句选 Qt6 安装包的事情同一个 Qt6 版本里MSVC 构建的库和 MinGW 构建的库 ABI 不互通Windows 上主程序用 MSVC kitQtXlsxWriter 也必须是同一个编译器链编出来的。这条规则和 QtXlsxWriter 自身的版本选择同样重要。3. 编译 QtXlsxWriter-qt6 源码包从解压 zip 到跑通第一个 xlsx 的三步命令3.1 解压 zip 后的目录确认先分清 qmake 工程和 CMake 工程先做 zip 解压。源码以 zip 包分发最常见的两种容器格式是带一层顶层目录和不带目录的裸源码前者解压后像 third_party/QtXlsxWriter-qt6/src后者直接是当前目录下的 src。这里有个小坑有些发布者在压缩时把项目文件夹直接拖进压缩工具解压出来变成 QtXlsxWriter-qt6/QtXlsxWriter-qt6 两层同名目录CMake 的 target 名和 include 路径都会受影响。Windows 上用右键解压时建议先把文件扩展名显示出来避免连 zip 扩展名一起改名另外部分安全软件会拦截含 dll 的第三方源码包解压到一半报错最典型的现象是 find 目录时找不到 src。unzip QtXlsxWriter-qt6.zip -d third_party cd third_party/QtXlsxWriter-qt6 find . -maxdepth 2 -type d | sortunzip 的-d参数指定解压目标目录防止在源码根目录散落一堆文件。解压后先看根目录有哪些文件。如果看到 CMakeLists.txt就走 CMake 流程如果只有 .pro / .pri说明这份源码还停留在 qmake 时代要么自己补一个 CMakeLists要么 qmake6 编译。我一般会优先选带 CMakeLists 的 Qt6 移植版因为后面 find_package(QXlsx) 的集成路径最短。3.2 CMake 构建的完整命令CMAKE_PREFIX_PATH 和 BUILD_SHARED_LIBS 两个关键参数cmake -S . -B build \ -DCMAKE_PREFIX_PATH/opt/Qt/6.7.0/gcc_64 \ -DCMAKE_BUILD_TYPERelease \ -DBUILD_SHARED_LIBSON cmake --build build -j$(nproc) cmake --install build --prefix $HOME/local/qxlsx三个参数里最关键的是 CMAKE_PREFIX_PATH。它告诉 CMake 去哪找 Qt6 和 QXlsx 的 CMake 配置而 Qt6 的安装路径和编译器是绑在一起的。你下载 Qt 时纠结的「qt6 最新版安装包选哪些」这类问题本质上就是在选 kit路径里的 gcc_64 表示 Linux 上用 GCC 编译的 64 位 Qt如果你本机还有 msys2、conda 里的其它 Qt6prefix 指错就会编出一个链接到错误 Qt 的库。BUILD_SHARED_LIBS 决定 QXlsx 自己编成动态库还是静态库。我建议 ON。静态库虽然部署方便但很多 QtXlsxWriter 移植版自带一份 pugixml你的工程里如果还有别的静态库也带了 pugixml链接期会撞符号这就是第 5 章要讲的坑。先编成动态库把这个变量打开后面省很多事。-j$(nproc)是并行编译Linux 上 nproc 能用macOS 没有 nproc改用-j$(sysctl -n hw.ncpu)Windows 下直接写cmake --build build -j8就行。安装前缀--prefix你要记住后面 find_package 的 CMAKE_PREFIX_PATH 要指到这里。提示Windows 上如果 CMake 提示找不到 Qt6先确认是不是在 bash 里把 Windows 的 Qt 路径和 WSL 里的 Qt 路径搞混了。Qt 的 CMake 配置对路径里的盘符和斜杠方向很敏感用cmake-gui看一遍更直观。如果你的源码包只有 qmake 工程备选方案是qmake6 make。但生成 Makefile 只是第一步你还得手动把 include 路径和库路径接进主工程Qt6 下 find_package 一条龙比这个省力得多。3.3 最小可运行示例CMakeLists.txt 加 main.cpp 生成第一份 xlsx主工程这边CMakeLists.txt 这样写cmake_minimum_required(VERSION 3.20) project(xlsx_min LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui) find_package(QXlsx REQUIRED) add_executable(xlsx_min main.cpp) target_link_libraries(xlsx_min PRIVATE Qt6::Core Qt6::Gui QXlsx::QXlsx)链接 Qt6::Gui 不是乱链多数用 QZipReader 的移植版依赖 QtGui 模块而 QGuiApplication 也为后面用 QFontMetrics 算列宽做准备。如果你的版本是 minizip 后端去掉 Qt6::Gui 只留 Core 也能编过但保留无害。QXlsx::QXlsx 是 find_package(QXlsx) 导出的 target安装前缀不对时这里会报找不到包回到 3.2 检查 CMAKE_PREFIX_PATH。#include QGuiApplication #include xlsxdocument.h int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QXlsx::Document xlsx; xlsx.write(1, 1, 设备名称); // 第 1 行第 1 列从 1 开始 xlsx.write(1, 2, 温度(°C)); // 表头直接走 sharedStrings bool ok xlsx.saveAs(report.xlsx); if (!ok) { return 1; // saveAs 失败必须处理见 5.2 } return 0; }write 的前两个参数是行和列第三个参数是 QVariantint、double、QString、QDateTime 都可以直接传。saveAs 返回 bool失败时文件可能只写了一半这个返回值要当成必须检查项。跑起来后ls -l report.xlsx确认文件存在再用unzip -l report.xlsx看内部结构你会看到 xl/worksheets/sheet1.xml 和 xl/sharedStrings.xml说明两个中文串被正确去重写入了共享字符串表。4. QtXlsxWriter 常用接口与参数写数值、合并单元格、日期格式的十个必调参数4.1 write 的多种重载与自动列宽数据类型的隐式转换和 QFontMetrics 计算xlsx.write(3, 1, 42); // int - 数值单元格 xlsx.write(3, 2, 3.14159); // double - 数值单元格 xlsx.write(3, 3, QDateTime::currentDateTime()); // QDateTime - 日期序列值 xlsx.write(3, 4, QStringLiteral(文本)); // 字符串 - sharedStringswrite 的第三个参数只要是 QVariant 能接受的类型就行数字会写进数值单元格字符串走 sharedStrings。这里容易踩的坑是把数字写成字符串write(3, 1, QString::number(42))之后 Excel 里它是文本无法参与求和、透视表。业务上要求可计算就传数字类型。列宽方面 QtXlsxWriter 没有 Excel 那种自动适应列宽的功能。常见做法是用 QFontMetrics 按内容估算宽度再 setColumnWidth宽度的单位是字符数QFontMetrics fm(app.font()); int w fm.horizontalAdvance(QStringLiteral(温度(°C))) / fm.horizontalAdvance(QLatin1Char(0)) 2; xlsx.setColumnWidth(2, w);加 2 是留出单元格边框和左右 padding 的余量标题行一般再乘 1.2。setColumnWidth 的第一个参数是列号从 1 数第二个是字符宽这个接口不认字母列号传 B 会直接编译报错。4.2 合并单元格与样式Format 的高频参数QXlsx::Format title; title.setFontSize(16); // 标题字号 title.setFontBold(true); // 加粗 title.setFontColor(Qt::white); title.setPatternBackgroundColor(QColor(#4472C4)); // 深蓝底 title.setHorizontalAlignment(Qt::AlignHCenter); title.setVerticalAlignment(Qt::AlignVCenter); QXlsx::Format body; body.setBorderStyle(QXlsx::Format::BorderThin); // 细边框 xlsx.mergeCells(A1:F1); // 合并 A1:F1 xlsx.write(1, 1, QStringLiteral(一车间温度巡检表), title); xlsx.write(2, 1, QStringLiteral(设备名称), body); xlsx.write(2, 2, QStringLiteral(温度(°C)), body);mergeCells 接收一个区域字符串格式是「起始列行:结束列行」A1:F1 表示合并第一行的 A 到 F。区域写反不会报错但合并结果不对跨 sheet 做不到每个合并都落在当前 sheet 上。Format 的高频参数就是代码里这些字体大小、加粗、字体颜色、单元格背景、水平对齐、垂直对齐、边框。背景色用 setPatternBackgroundColor 加 QColor传#4472C4会被 Qt 转成 ARGBExcel 里看到的就是深蓝底。注意对齐枚举在不同移植版里写法不同有的版本 setHorizontalAlignment 收 Qt::AlignHCenter有的版本要求 QXlsx::Format::AlignHCenter。编译不过时打开 xlsxformat.h 看 enum 定义一分钟就能确认。Format 属性是叠加式的先 setFontBold(true) 再 setBorderStyle(...)两个都生效想清零某个属性没有对应的 unset直接 new 一个干净的 Format 更省事。4.3 日期与数字格式Excel 序列值与 setNumberFormat 的配合QXlsx::Format dateFmt; dateFmt.setNumberFormat(yyyy-mm-dd); QDateTime t(QDate(2024, 5, 1), QTime(9, 30, 0)); xlsx.write(4, 1, t, dateFmt); // 库自动转日期序列 xlsx.write(4, 2, t.toMSecsSinceEpoch() / 86400000.0 25569.0, dateFmt); // 手动转Excel 不存日期存的是从 1899-12-30 开始的天数序列1900 年 1 月 1 日就是 1。QtXlsxWriter 收到 QDateTime 时会自动完成这个转换前提是第四参数传了带日期格式的 Format。如果你 write 一个 QString 2024-05-01Excel 只把它当文本日期计算、筛选都失灵。第二行是手动算序列值的写法25569 是 Unix 时间戳起点 1970-01-01 对应的 Excel 序列号。为什么需要手动写因为默认转换按本地时区算跨国业务要固定到 UTC 时直接算好序列值塞进去比改库的默认行为更可控。提示日期序列的 25569 偏移针对 1900 日期系统。如果客户用 mac 版 Excel 的 1904 日期系统同一份文件打开日期会差 1462 天交付前先确认对方的 Excel 设置。数字格式同理setNumberFormat(0.00)让 76.3 显示成 76.30#,##0.00带千分位0.00%适合百分率。setNumberFormat 只影响显示不影响存储值Excel 单元格里的原始 double 不变。5. QtXlsxWriter 避坑记录编译失败、文件损坏与中文乱码的五个排查案例下面五条按「现象 → 原因 → 解决」写都是实际接入 QtXlsxWriter 时出现频率最高的。5.1 坑 1头文件找不到 xlsxdocument.h现象主程序 include xlsxdocument.h 后编译报 fatal error: xlsxdocument.h: No such file or directory。原因find_package(QXlsx) 没找到包或者找到了但 target 没把 include 目录带出来。多数移植版安装后的头文件在 include/QXlsx 下你的工程只链了库没加头文件搜索路径。解决先用 find_package 的报错信息确认搜索路径临时验证可以加-DCMAKE_PREFIX_PATH...重跑 cmake。更省事的写法是在 CMakeLists 里显式加find_package(QXlsx REQUIRED) target_include_directories(xlsx_min PRIVATE ${QXlsx_INCLUDE_DIRS}) target_link_libraries(xlsx_min PRIVATE QXlsx::QXlsx)另一个高频原因主工程用 MSVC而 QtXlsxWriter 是用 MinGW 编的。包找到了编译还是挂属于 ABI 不匹配统一到同一套工具链重新编译两边才能解决。5.2 坑 2生成的 xlsx 打开报文件损坏unzip 提示 could not find eocd现象程序运行完没报错Excel / WPS 打开文件提示「文件已损坏是否尝试修复」用 unzip -t 或 openpyxl 读取时报错 could not find eocd。原因eocd 是 zip 格式的中央目录记录写在文件末尾找不到 eocd 只有一个解释——文件不完整。最常见的原因是 saveAs 返回 false 而代码没检查目标目录不存在、路径只读、磁盘满或者文件正被 Excel 打开占用。写了一半的 zip 文件尾部缺失Excel 拿到这种残次品当然不认。这个报错在 zip 协议相关的导入场景里是个通病凡是看到 could not find eocd先怀疑文件被截断。解决把 saveAs 的返回值当成强制检查项。再稳妥一点先用临时文件名保存成功后 rename 到正式路径bool ok xlsx.saveAs(report.tmp.xlsx); if (!ok) { return 1; // 失败立刻退出不要继续往下走 } QFile::remove(report.xlsx); // 先删旧文件暴露占用问题 QFile::rename(report.tmp.xlsx, report.xlsx);删旧文件这步能顺手把「旧文件被占用导致写不进去」这个隐患暴露出来。验证时用unzip -t report.xlsx输出 OK 再交付。5.3 坑 3中文在 Excel 里乱码现象程序输出的 xlsx文本编辑器打开 sheet XML 是正常的 UTF-8Excel 里却显示乱码更常见的变体是 Windows 控制台里打印读回结果乱码。原因xlsx 规范要求字符串以 UTF-8 存在 XML 里QtXlsxWriter 写的是字节层面的 UTF-8。乱码通常发生在源头MSVC 默认把无 BOM 的源文件按本地代码页GBK解释你源码里的 QStringLiteral(中文) 在编译期就已经变成错误字符后面全是徒劳。另一种是自己用了 QString::fromLocal8Bit 去拼字符串。解决MSVC 下加/utf-8编译选项源码文件统一 UTF-8字符串一律 QStringLiteral不要 fromLocal8Bitif(MSVC) target_compile_options(xlsx_min PRIVATE /utf-8) endif()判断根因的快速方法读回后把字符串转成 QByteArray用 hex 打印前几个字节。UTF-8 的「设」是 E8 AE BEGBK 是 C9 E8。看到 C9 E8 就说明源头已经错了不是库的问题。5.4 坑 4链接期 pugixml 符号冲突静态库打架的三种解法现象链接阶段刷屏 multiple definition符号名都带 pugixml::例如 pugixml::xml_document::load_buffer。原因很多 QtXlsxWriter 移植版自带的 XML 解析用的是 pugixml源码放在 third_party/pugixml 目录。如果你的工程里另一个静态库也编译了同一份 pugixml两个库的符号在最终链接时冲突。BUILD_SHARED_LIBSOFF 时最容易撞。解决三个方向。第一把 QXlsx 编成动态库冲突范围大幅缩小这是最省事的第二你自己工程里也有一份 pugixml 源码时把 QtXlsxWriter 源包中 third_party/pugixml 的编译目标从 CMake 列表里注掉统一用你那份第三不推荐用-Wl,--allow-multiple-definition这种压制报错的选项掩盖问题等于给后面埋雷。先改 BUILD_SHARED_LIBSON 重编九成情况直接解决。5.5 坑 5发布后目标机器找不到 QXlsx 动态库现象本机运行正常拷贝 exe 到没装 Qt 的机器上双击启动直接提示找不到 qxlsx.dll 或 zlib1.dll。原因windeployqt 只部署 Qt 自身的运行库第三方 QXlsx 的 dll 不在它的清单里。MinGW 构建下的 zlib1.dll、LIBGCC 系列 dll 更容易漏。解决发布脚本里手动拷贝 QXlsx 的 dll/so/dylib再跑 windeployqt 补齐 Qt 的库。验证依赖用工具Windows 上 dumpbin /dependents app.exeVS 自带Linux 上用 lddldd ./xlsx_min | grep -i -E xlsx|qt6发布物里的库路径必须是相对路径或系统路径出现/home/xxx/Qt/...这类绝对路径说明部署还没做完。6. 进阶与验证十万行批量写入的性能习惯以及用 openpyxl 回读确认6.1 十万行数据瓶颈在 saveAs 的压缩不在逐格 write先说结论十万行以内的逐格 write 是内存操作代价可接受真正的耗时在最后的 saveAs——它要把内存里的 XML 序列化并 zip 压缩落盘。所以优化方向不是改写入方式而是减少序列化负担复用一个 Format 对象而不是每格 new 一个只写有数据的 sheet别让空的默认 sheet 一起落盘字符串尽量短因为 sharedStrings 要驻留内存参与去重。用 QElapsedTimer 在 saveAs 前后各打一次点先量化再优化别凭感觉改。6.2 收尾验证unzip -t 和 Python openpyxl 回读unzip -t report.xlsx python3 -c import openpyxl wb openpyxl.load_workbook(report.xlsx) ws wb.active for row in ws.iter_rows(values_onlyTrue): print(row) unzip -t 检验 zip 完整性eocd 缺失在这里会直接暴露。openpyxl 回读是第二道保险它不关心样式只看结构sharedStrings 索引错位、sheet 关系丢失这类问题Excel 会尝试「修复」openpyxl 直接抛异常。两道检查都过了文件再交给客户就稳了。6.3 我保留的一个习惯写完立刻读回最后是我用血泪换来的习惯每次 saveAs 之后紧接着用同一个程序读回一遍而不是等客户打开才发现坏了。QXlsx::Document check(report.xlsx); QVariant v check.read(2, 2); // 读回刚才写入的单元格 if (!v.isValid()) { return 1; // 写读不一致立刻处理 }读回这一步会把「格式正确但内容错位」这类最隐蔽的问题挡在交付前。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?