加载中...
libxlsxwriter-让数据说话
第1节:libfswatch-文件变动监控
第2节:libiconv-字符集编码转换
第3节:CLI11-命令行参数解析
第4节:nlohmann/json-自然的JSON库
第5节:libb64-理解并玩转base64编码
第6节:libSnappy-快速压缩工具
第7节:spdlog-首选的C++日志库
第8节:libUSB-脱掉USB的外套
第9节:libxlsxwriter-让数据说话
课文封面

libxlsxwriter可以帮我们轻松将程序数据,导成 Excel 文件(.xlsx 格式),并且,所支持的格式极为丰富、完整。

把数据导成 Excel 文件,用户可用 MS Office 或 WPS 打开继续编辑,这是一项能给用户带来巨大方便的功能,任何一个程序员都应该掌握这项技能。

0. 让数据说话

会做还要会说,辛苦三个月,系统上线——猜猜用户最容易挑的毛病在哪个范畴?答:界面。更进一步的,界面中的数据展现。

——不要怪用户要求多——事实上,他是被逼的。用户头上有更高层的领导,而高层领导基本看都不看你的软件一眼,更别说试用一下,感受感受开发人员最引以为傲的 “丝滑” 的操作。

——不要怪领导不接地气还意见多——任何一个软件系统,本质是工具而非目标。目标是什么?对于多数高层领导来说,目标是数据,目标是图表。即:软件系统是用来产生数据,特别是一眼可看懂的图表数据的工具之一而已。

有一个功能,以我30多年的开发经验,几乎任何一个软件系统加上它之后,用户、领导、甚至同事,都将异口同声叫好,这个功能就是:将数据导出为 Excel 文件。

功能示例:

导出Excel功能入口示例

1. 快速认识

安装

  • Windows + msys2 环境 (ucrt64 子环境):pacman -S mingw-w64-ucrt-x86_64-libxlsxwriter
  • UBUNTU 开发机或服务器:apt -install libxlsxwriter-dev;非服务器需 sudo。

基本概念

  • Workbook (工作簿):对应一个 Excel 文件(仅支持扩展名: .xlsx);
  • Worksheet (工作页):对应 Excel 文件中“页”。

CMake 项目文件 CMakelists.txt

cmake_minimum_required(VERSION 3.15) PROJECT(HelloExcel) SET(CMAKE_CXX_STANDARD 17) add_executable(HelloExcel main.cpp) target_link_libraries(HelloExcel xlsxwriter) # 以下两个路径,请替换为你的路径 target_link_directories(HelloExcel PRIVATE c:/msys64/ucrt64/lib/) target_include_directories(HelloExcel PRIVATE c:/msys64/ucrt64/include)

1.1 课堂视频-1

1.2 代码

  • main.cpp
#include <xlsxwriter.h> int main() { lxw_workbook *workbook = workbook_new("hello.xlsx"); lxw_worksheet *worksheet = workbook_add_worksheet(workbook, nullptr); worksheet_write_string(worksheet, 0, 0, "你好啊,Excel 表格!", nullptr); workbook_close(workbook); }

2. 更多功能

2.1 课堂视频-2

2.2 代码

  • main.cpp
#include <string> #include <memory> // 智能指针 #include <sstream> // 字符串流 #include <xlsxwriter.h> struct YMD { int y, m, d; // 年月日 }; lxw_datetime to_datetime(YMD const& ymd) { return {ymd.y, ymd.m, ymd.d, 0, 0, 0.0}; } struct Fruit { std::string name; // 水果名称 double price; // 价格 int totalSales; // 总销售量 YMD offline; // 下架日期 }; void prices() // 价目表 { Fruit const products [] = { {"苹果", 25.55, 5600, {2025, 11, 30} }, {"香蕉", 36.80, 280, {2026, 1, 13} }, {"橙子", 67.11, 1205, {2026, 2, 14} }, {"葡萄", 120.03, 673, {2025, 6, 15} }, {"桃子", 18.50, 193, {2026, 4, 16} }, }; lxw_workbook *workbook = workbook_new("prices.xlsx"); // 哨兵 std::unique_ptr<lxw_workbook, lxw_error(*)(lxw_workbook*)> guard_workbook(workbook, workbook_close); lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "报价清单"); // 创建“钱”的格式 lxw_format *fmt_money = workbook_add_format(workbook); format_set_num_format(fmt_money, "¥#,##0.00"); // 创建“日期”的格式 lxw_format *fmt_date = workbook_add_format(workbook); format_set_num_format(fmt_date, "yyyy-mm-dd"); // 显式设置第1-5列宽度及格式 worksheet_set_column(worksheet, 0, 0, 12.5, nullptr); // 名字列宽 worksheet_set_column(worksheet, 1, 1, 16.0, fmt_money); // 价格列宽 worksheet_set_column(worksheet, 2, 2, 14.5, nullptr); // 销售量列宽 worksheet_set_column(worksheet, 3, 3, 14.0, fmt_money); // 收入列宽 worksheet_set_column(worksheet, 4, 4, 16.0, fmt_date); // 下架日期列宽 // 准备表头格式 lxw_format* fmt_header = workbook_add_format(workbook); format_set_bold(fmt_header); // 加粗 format_set_font_size(fmt_header, 12); //字号 format_set_font_color(fmt_header, LXW_COLOR_WHITE); // 字体颜色:白 format_set_bg_color(fmt_header, 0x4F81BD); // 背景颜色:灰蓝色 format_set_align(fmt_header, LXW_ALIGN_CENTER); // 居中对齐 int row = 0; // 写入列标题(在第1行) worksheet_write_string(worksheet, row, 0, "水果", fmt_header); worksheet_write_string(worksheet, row, 1, "单价(元/斤)", fmt_header); worksheet_write_string(worksheet, row, 2, "销量(斤)", fmt_header); worksheet_write_string(worksheet, row, 3, "收入(元)", fmt_header); worksheet_write_string(worksheet, row, 4, "下架日期", fmt_header); lxw_format* fmt_name = workbook_add_format(workbook); format_set_bold(fmt_name); // 加粗 format_set_align(fmt_name, LXW_ALIGN_CENTER); // 居中对齐 for (auto const& product : products) { row++; worksheet_write_string(worksheet, row, 0, product.name.c_str(), fmt_name); worksheet_write_number(worksheet, row, 1, product.price, nullptr); worksheet_write_number(worksheet, row, 2, product.totalSales, nullptr); std::stringstream ss; ss << "=B" << row + 1 << "*C" << row + 1; worksheet_write_formula(worksheet, row, 3, ss.str().c_str(), nullptr); auto offline = to_datetime(product.offline); worksheet_write_datetime(worksheet, row, 4, &offline, nullptr); } // 加上斑马线效果 // 1. 创建背景色格式 auto fmt_bkgnd = workbook_add_format(workbook); format_set_bg_color(fmt_bkgnd, 0xDAEEF3); // 背景颜色:浅蓝色 // 2. 设置条件格式(使用 Excel 公式检查) lxw_conditional_format cf {}; // 条件格式结构体 cf.type = LXW_CONDITIONAL_TYPE_FORMULA; // 指定使用公式检查 cf.value_string = "MOD(ROW(),2)<>0"; // 奇数行判定公式 cf.format = fmt_bkgnd; // 应用背景色格式 // 3. 在指定范围(起始行列~结束行列)的单元格中检查,应用条件格式 worksheet_conditional_format_range(worksheet, 1, 1, std::size(products) - 1, 4, &cf); // 锁定表头 worksheet_freeze_panes(worksheet, 1, 1); } int main() { prices(); }

3. 涉及函数说明

函数 说明
workbook_new(“文件名”) 创建新工作簿,如文件名包含UTF8汉字,建议使用libiconv转为GBK
workbook_add_worksheet(workbook,“name”) 在workbook上添加新工作页;name为页(标签)名
workbook_close(workbook) 关闭工作簿,回收其下资源
worksheet_write_string(worksheet, row, col,“字符串”,fmt) 在指定页,指定行列(从0起)以fmt格式,写入字符串。fmt 为空(nullptr)表示使用默认格式
worksheet_write_number(worksheet, row, col, number, fmt) 同上,但写入数据必须是数值(int 或 double)
worksheet_write_formula(worksheet, row, col, “公式”, fmt) 同上,但写入数据必须是 Excel 公式
worksheet_write_datetime(worksheet, row, col, datetime, fmt) 同上,但写入数据需为 lxw_datetime*
workbook_add_format(workbook) 创建新格式数据(lxw_format*)
format_set_num_format(fmt, “数字格式”) 设置数字展现格式(包括数值、货币、日期等)
format_set_bold(fmt) 设置字体加粗
format_set_font_size(fmt, size) 设置字号
format_set_font_color(fmt, color) 设置字体颜色,颜色可使用预定义宏,如:LXW_COLOR_WHITE 或自行组装RGB,如:0xFF0000
format_set_bg_color(fmt, color) 设置背景颜色
format_set_align(fmt, 对齐方式) 对齐方式:LXW_ALIGN_LEFT, LXW_ALIGN_CENTER, LXW_ALIGN_RIGHT, LXW_ALIGN_FILL, LXW_ALIGN_JUSTIFY…
worksheet_set_column(worksheet, beg, end, 宽度, fmt) 设置 beg 列到 end 列的宽度与格式(格式对整列起作用,哪怕单元格中没有数据)
worksheet_conditional_format_range(worksheet, 起始行, 起始列, 结束行, 结束列, 条件格式) 在指定范围内,将符合指定条件的单元格,设置为指定格式。具体见 lxw_conditional_format 结构体定义