0. 让数据说话
会做还要会说,辛苦三个月,系统上线——猜猜用户最容易挑的毛病在哪个范畴?答:界面。更进一步的,界面中的数据展现。
——不要怪用户要求多——事实上,他是被逼的。用户头上有更高层的领导,而高层领导基本看都不看你的软件一眼,更别说试用一下,感受感受开发人员最引以为傲的 “丝滑” 的操作。
——不要怪领导不接地气还意见多——任何一个软件系统,本质是工具而非目标。目标是什么?对于多数高层领导来说,目标是数据,目标是图表。即:软件系统是用来产生数据,特别是一眼可看懂的图表数据的工具之一而已。
有一个功能,以我30多年的开发经验,几乎任何一个软件系统加上它之后,用户、领导、甚至同事,都将异口同声叫好,这个功能就是:将数据导出为 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 结构体定义 |