😁博客主页😁:🚀https://blog.youkuaiyun.com/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍导出 QTableWidget 数据到 Excel 的 4 种方法 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰:
本文未经允许,不得转发!!!
目录

![]()
🎄一、概述
在 Qt 应用程序开发中,数据导出是常见的需求。QTableWidget 作为 Qt 中常用的数据展示组件,经常需要将其内容导出为 Excel 格式以便于分享或进一步分析。
本文将系统性地介绍 四种实现方法,涵盖从最简单的纯文本方案到功能完整的第三方库方案。每种方法均被封装为独立的导出函数,只需将 QTableWidget 指针 作为参数传入即可使用。
![]()
🎄二、方法一:CSV 格式导出(最简方案)
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。
这是最快速、兼容性最好的方案。CSV 文件可以被几乎所有电子表格软件(Excel、WPS、LibreOffice)识别。
核心思路:将表格数据格式化为逗号分隔的文本文件,并正确处理特殊字符。
实现代码与步骤
此函数完成了:文件保存对话框、数据格式化、中文编码处理和完整的用户反馈。
/**
* @brief 将 QTableWidget 数据导出为 CSV 文件
* @param tableWidget 需要导出的表格控件指针
* @return true 导出成功,false 导出失败
*/
bool saveTableWidgetToCSV(QTableWidget *tableWidget)
{
// 1. 参数有效性检查
if (!tableWidget) {
QMessageBox::warning(nullptr, "导出失败", "表格对象为空");
return false;
}
if (tableWidget->rowCount() == 0 && tableWidget->columnCount() == 0) {
QMessageBox::warning(nullptr, "导出失败", "表格数据为空");
return false;
}
// 2. 获取保存路径
QString fileName = QFileDialog::getSaveFileName(nullptr,
"保存为CSV文件", "data.csv", "CSV文件 (*.csv)");
if (fileName.isEmpty()) return false;
// 3. 确保文件扩展名
if (!fileName.endsWith(".csv", Qt::CaseInsensitive)) {
fileName += ".csv";
}
// 4. 创建文件并写入数据
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(nullptr, "导出失败",
QString("无法创建文件:\n%1").arg(fileName));
return false;
}
QTextStream out(&file);
out.setCodec("UTF-8"); // 关键:设置UTF-8编码支持中文
// 5. 写入表头
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *header = tableWidget->horizontalHeaderItem(col);
if (header) {
// 处理包含逗号或引号的单元格内容
QString text = header->text().replace("\"", "\"\"");
out << "\"" << text << "\"";
} else {
out << QString("列%1").arg(col + 1);
}
if (col < tableWidget->columnCount() - 1) out << ",";
}
out << "\n";
// 6. 写入数据行
int exportedRows = 0;
for (int row = 0; row < tableWidget->rowCount(); ++row) {
bool rowHasData = false;
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *item = tableWidget->item(row, col);
if (item && !item->text().isEmpty()) {
rowHasData = true;
}
if (item) {
QString text = item->text().replace("\"", "\"\"");
out << "\"" << text << "\"";
}
if (col < tableWidget->columnCount() - 1) out << ",";
}
out << "\n";
if (rowHasData) exportedRows++;
}
file.close();
// 7. 导出结果反馈
if (exportedRows == 0 && tableWidget->columnCount() > 0) {
QMessageBox::information(nullptr, "导出完成",
"文件已保存,但所有行数据为空");
return true;
}
//QMessageBox::information(nullptr, "导出成功",
// QString("数据已成功导出到:\n%1\n\n总计导出: %2 行 × %3 列")
// .arg(fileName).arg(exportedRows).arg(tableWidget->columnCount()));
QString message = QString("数据已成功导出到:\n%1\n\n"
"总计导出: %2 行 × %3 列\n\n"
"说明:此文件csv格式,可被Excel/WPS等表格软件打开")
.arg(fileName).arg(exportedRows).arg(tableWidget->columnCount());
QMessageBox msgBox(QMessageBox::Information, "导出成功", message);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Open);
msgBox.setButtonText(QMessageBox::Open, "打开文件");
if (msgBox.exec() == QMessageBox::Open) {
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
return true;
}
运行结果:

优点:
-
无需任何第三方库
-
跨平台兼容性好
-
实现简单,代码量少
缺点:
-
导出的是纯文本格式,非原生 Excel 文件
-
不支持单元格格式、公式等高级特性
![]()
🎄三、方法二:制表符分隔方案(改良版)
这是对 CSV 方案的优化,使用制表符 (\t) 作为分隔符,并以 .xls 或 .xlsx 扩展名保存,Excel 会自动识别为表格数据。
实现代码
/**
* @brief 使用制表符分隔将 QTableWidget 数据导出为 Excel 可识别的文件
* @param tableWidget 需要导出的表格控件指针
* @return true 导出成功,false 导出失败
*/
bool saveTableWidgetToExcelWithTab(QTableWidget *tableWidget)
{
// 1. 参数有效性检查
if (!tableWidget) {
QMessageBox::warning(nullptr, "导出失败", "表格对象为空");
return false;
}
if (tableWidget->rowCount() == 0 && tableWidget->columnCount() == 0) {
QMessageBox::warning(nullptr, "导出失败", "表格数据为空");
return false;
}
// 2. 获取保存路径
QString fileName = QFileDialog::getSaveFileName(nullptr,
"保存为Excel文件", "data.xls",
"Excel文件 (*.xls *.xlsx);;文本文件 (*.txt);;所有文件 (*.*)");
if (fileName.isEmpty()) return false;
// 3. 确保文件扩展名
if (!fileName.endsWith(".xls", Qt::CaseInsensitive) &&
!fileName.endsWith(".xlsx", Qt::CaseInsensitive) &&
!fileName.endsWith(".txt", Qt::CaseInsensitive)) {
fileName += ".xls";
}
// 4. 创建文件并写入数据
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(nullptr, "导出失败",
QString("无法创建文件:\n%1").arg(fileName));
return false;
}
QTextStream out(&file);
out.setCodec("UTF-8"); // 关键:设置UTF-8编码支持中文
// out.setGenerateByteOrderMark(true);
try {
int exportedRows = 0;
// 5. 写入表头
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *header = tableWidget->horizontalHeaderItem(col);
if (header) {
QString text = header->text();
text.replace("\t", " ");
text.replace("\n", " ");
text.replace("\r", "");
out << text;
} else {
out << QString("列%1").arg(col + 1);
}
if (col < tableWidget->columnCount() - 1) {
out << "\t";
}
}
out << "\n";
// 6. 写入数据行
for (int row = 0; row < tableWidget->rowCount(); ++row) {
bool rowHasData = false;
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *item = tableWidget->item(row, col);
if (item) {
QString text = item->text();
// 处理特殊字符
text.replace("\t", " ");
text.replace("\n", " ");
text.replace("\r", "");
// 检查是否是数字
bool isNumber = false;
bool ok;
text.toDouble(&ok);
if (ok && !text.isEmpty() &&
(text[0].isDigit() || text[0] == '-' || text[0] == '+')) {
isNumber = true;
}
// 如果以等号开头,添加单引号(防止Excel识别为公式)
if (!isNumber && (text.startsWith("=") || text.startsWith("+") ||
text.startsWith("-") || text.startsWith("@"))) {
text = "'" + text;
}
out << text;
if (!text.isEmpty()) {
rowHasData = true;
}
}
if (col < tableWidget->columnCount() - 1) {
out << "\t";
}
}
out << "\n";
if (rowHasData) exportedRows++;
}
file.close();
// 7. 导出结果反馈
QString fileType = QFileInfo(fileName).suffix().toUpper();
QString message;
if (exportedRows == 0) {
message = QString("文件已保存为 %1 格式:\n%2\n\n但所有行数据为空")
.arg(fileType).arg(fileName);
QMessageBox::information(nullptr, "导出完成", message);
} else {
message = QString("数据已成功导出为 %1 格式:\n%2\n\n"
"总计导出: %3 行 × %4 列\n\n"
"说明:此文件使用制表符分隔,可被Excel/WPS等表格软件打开")
.arg(fileType).arg(fileName)
.arg(exportedRows).arg(tableWidget->columnCount());
QMessageBox msgBox(QMessageBox::Information, "导出成功", message);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Open);
msgBox.setButtonText(QMessageBox::Open, "打开文件");
if (msgBox.exec() == QMessageBox::Open) {
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
}
return true;
} catch (...) {
file.close();
QFile::remove(fileName);
QMessageBox::critical(nullptr, "导出失败", "导出过程中发生错误");
return false;
}
}
运行结果

优点:
-
Excel 打开时自动识别为表格,视觉体验更好
-
比 CSV 更少遇到文本内容含分隔符导致错位的问题
![]()
🎄四、方法三:使用 QAxObject(Windows 专属方案)
此方法通过 Qt 的 ActiveX 支持直接操作 Excel 应用程序,可以生成功能完整的原生 Excel 文件。
实现前提:仅限 Windows 平台,且系统中需安装 Microsoft Excel。
.pro 文件配置:
QT += axcontainer # 添加 ActiveX 支持
实现代码:
// 辅助函数:检测Excel是否安装
bool isExcelInstalled()
{
// 方法1:检查注册表
QSettings registry("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
// 检查不同版本的Excel
QStringList excelProgIDs = {
"Excel.Application", // 默认
"Excel.Application.16", // Office 2016/365
"Excel.Application.15", // Office 2013
"Excel.Application.14", // Office 2010
"Excel.Application.12" // Office 2007
};
foreach (const QString &progID, excelProgIDs) {
if (registry.contains(progID + "/CLSID")) {
qDebug() << "检测到Excel安装:" << progID;
return true;
}
}
// 方法2:检查系统路径中的excel.exe
QStringList systemPaths = QString(getenv("PATH")).split(";");
foreach (const QString &path, systemPaths) {
if (QFile::exists(path + "/excel.exe") ||
QFile::exists(path + "/EXCEL.EXE")) {
qDebug() << "在系统路径中找到excel.exe";
return true;
}
}
// 方法3:检查常见的Excel安装路径
QStringList commonPaths;
// Office 365/2019/2016
commonPaths << "C:/Program Files/Microsoft Office/root/Office16/EXCEL.EXE";
commonPaths << "C:/Program Files (x86)/Microsoft Office/root/Office16/EXCEL.EXE";
// Office 2013
commonPaths << "C:/Program Files/Microsoft Office/Office15/EXCEL.EXE";
commonPaths << "C:/Program Files (x86)/Microsoft Office/Office15/EXCEL.EXE";
// Office 2010
commonPaths << "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE";
commonPaths << "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE";
// Office 2007
commonPaths << "C:/Program Files/Microsoft Office/Office12/EXCEL.EXE";
commonPaths << "C:/Program Files (x86)/Microsoft Office/Office12/EXCEL.EXE";
foreach (const QString &path, commonPaths) {
if (QFile::exists(path)) {
qDebug() << "在常见路径中找到Excel:" << path;
return true;
}
}
qDebug() << "未检测到Excel安装";
return false;
}
// 辅助函数:获取已安装的Excel版本
QString getInstalledExcelVersion()
{
QSettings registry("HKEY_CLASSES_ROOT", QSettings::NativeFormat);
QStringList versions = {
"Excel.Application.16", // Office 2016/365
"Excel.Application.15", // Office 2013
"Excel.Application.14", // Office 2010
"Excel.Application.12", // Office 2007
"Excel.Application" // 默认
};
foreach (const QString &version, versions) {
if (registry.contains(version + "/CLSID")) {
if (version == "Excel.Application.16") return "Office 2016/365";
if (version == "Excel.Application.15") return "Office 2013";
if (version == "Excel.Application.14") return "Office 2010";
if (version == "Excel.Application.12") return "Office 2007";
return "Office (未知版本)";
}
}
return "未安装";
}
bool saveTableWidgetToExcelWithQAxObject(QTableWidget *tableWidget)
{
if (!tableWidget) {
QMessageBox::warning(nullptr, "导出失败", "表格对象为空");
return false;
}
// 1. 首先检查Excel是否安装
if (!isExcelInstalled()) {
QString errorMsg = "未检测到 Microsoft Excel 安装。\n\n";
errorMsg += "请安装以下任一版本的 Microsoft Excel:\n";
errorMsg += "• Office 2007 或更高版本\n";
errorMsg += "• Office 365\n\n";
errorMsg += "或者使用其他导出格式(如CSV或制表符分隔格式)。";
QMessageBox::critical(nullptr, "Excel未安装", errorMsg);
return false;
}
// 2. 获取已安装的Excel版本信息(用于调试)
QString excelVersion = getInstalledExcelVersion();
qDebug() << "检测到Excel版本:" << excelVersion;
// 3. 获取保存文件名
QString fileName = QFileDialog::getSaveFileName(nullptr,
"保存为Excel文件 (使用" + excelVersion + ")",
"data.xlsx",
"Excel文件 (*.xlsx *.xls)");
if (fileName.isEmpty()) {
return false; // 用户取消
}
if (!fileName.endsWith(".xlsx", Qt::CaseInsensitive) &&
!fileName.endsWith(".xls", Qt::CaseInsensitive)) {
fileName += ".xlsx";
}
// 4. 显示导出进度对话框
QProgressDialog progressDialog("正在导出Excel文件...", "取消导出", 0, 100, nullptr);
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setMinimumDuration(0);
progressDialog.setValue(10);
QCoreApplication::processEvents();
// 5. 尝试创建Excel对象(使用异常安全的方式)
QAxObject *excel = nullptr;
QAxObject *workbooks = nullptr;
QAxObject *workbook = nullptr;
QAxObject *worksheet = nullptr;
try {
progressDialog.setLabelText("正在启动Excel...");
progressDialog.setValue(20);
QCoreApplication::processEvents();
// 创建Excel应用程序对象
excel = new QAxObject();
if (!excel) {
throw std::runtime_error("无法创建Excel对象");
}
// 尝试不同版本的Excel ProgID
QStringList excelProgIDs = {
"Excel.Application", // 默认
"Excel.Application.16", // Office 2016/365
"Excel.Application.15", // Office 2013
"Excel.Application.14", // Office 2010
"Excel.Application.12" // Office 2007
};
bool excelStarted = false;
QString usedProgID;
foreach (const QString &progID, excelProgIDs) {
if (excel->setControl(progID)) {
excelStarted = true;
usedProgID = progID;
qDebug() << "成功启动Excel:" << progID;
break;
}
}
if (!excelStarted) {
throw std::runtime_error("无法启动Excel应用程序。\n"
"可能的原因:\n"
"1. Excel未正确安装\n"
"2. Excel COM组件损坏\n"
"3. 权限不足");
}
progressDialog.setLabelText("正在设置Excel参数...");
progressDialog.setValue(30);
QCoreApplication::processEvents();
// 设置Excel属性
excel->setProperty("Visible", false);
excel->setProperty("DisplayAlerts", false);
// 创建工作簿
workbooks = excel->querySubObject("Workbooks");
if (!workbooks || workbooks->isNull()) {
throw std::runtime_error("无法访问Excel工作簿");
}
progressDialog.setLabelText("正在创建工作簿...");
progressDialog.setValue(40);
QCoreApplication::processEvents();
workbook = workbooks->querySubObject("Add");
if (!workbook || workbook->isNull()) {
throw std::runtime_error("无法创建新工作簿");
}
// 获取第一个工作表
worksheet = workbook->querySubObject("Worksheets(int)", 1);
if (!worksheet || worksheet->isNull()) {
throw std::runtime_error("无法访问工作表");
}
progressDialog.setLabelText("正在写入表头...");
progressDialog.setValue(50);
QCoreApplication::processEvents();
// 写入表头
int totalRows = tableWidget->rowCount();
int totalCols = tableWidget->columnCount();
int totalCells = totalRows * totalCols;
int processedCells = 0;
for (int col = 0; col < totalCols; ++col) {
QTableWidgetItem *header = tableWidget->horizontalHeaderItem(col);
QString headerText = header ? header->text() : QString("列%1").arg(col + 1);
// 使用Range对象写入
QString cellAddress = QString("%1%2").arg(QChar('A' + col)).arg(1);
QAxObject *range = worksheet->querySubObject("Range(const QString&)", cellAddress);
if (range) {
range->setProperty("Value", headerText);
// 尝试设置表头样式(可选)
QAxObject *font = range->querySubObject("Font");
if (font) {
font->setProperty("Bold", true);
delete font;
}
delete range;
}
}
progressDialog.setLabelText("正在写入数据...");
progressDialog.setValue(60);
QCoreApplication::processEvents();
// 写入数据
for (int row = 0; row < totalRows; ++row) {
for (int col = 0; col < totalCols; ++col) {
QTableWidgetItem *item = tableWidget->item(row, col);
if (item && !item->text().isEmpty()) {
QString cellAddress = QString("%1%2").arg(QChar('A' + col)).arg(row + 2);
QAxObject *range = worksheet->querySubObject("Range(const QString&)", cellAddress);
if (range) {
// 直接写入文本,避免类型转换问题
range->setProperty("Value", item->text());
delete range;
}
}
processedCells++;
// 更新进度
if (processedCells % 100 == 0) {
int progress = 60 + (processedCells * 30 / totalCells);
progressDialog.setValue(progress);
progressDialog.setLabelText(QString("正在写入数据... (%1/%2)")
.arg(processedCells).arg(totalCells));
QCoreApplication::processEvents();
// 检查是否取消
if (progressDialog.wasCanceled()) {
throw std::runtime_error("用户取消了导出操作");
}
}
}
}
progressDialog.setLabelText("正在调整列宽...");
progressDialog.setValue(90);
QCoreApplication::processEvents();
// 尝试自动调整列宽(如果失败也不影响主要功能)
QAxObject *usedRange = worksheet->querySubObject("UsedRange");
if (usedRange) {
QAxObject *columns = usedRange->querySubObject("Columns");
if (columns) {
columns->dynamicCall("AutoFit()");
delete columns;
}
delete usedRange;
}
progressDialog.setLabelText("正在保存文件...");
progressDialog.setValue(95);
QCoreApplication::processEvents();
// 保存文件
QVariant saveResult = workbook->dynamicCall("SaveAs(const QString&)",
QDir::toNativeSeparators(fileName));
if (!saveResult.toBool()) {
qDebug() << "保存返回值:" << saveResult;
// 即使返回false,文件可能已经保存,所以不抛出异常
}
progressDialog.setValue(100);
QCoreApplication::processEvents();
// 关闭Excel
workbook->dynamicCall("Close()");
excel->dynamicCall("Quit()");
// 清理资源
if (worksheet) {
delete worksheet;
worksheet = nullptr;
}
if (workbook) {
delete workbook;
workbook = nullptr;
}
if (workbooks) {
delete workbooks;
workbooks = nullptr;
}
if (excel) {
delete excel;
excel = nullptr;
}
// 验证文件是否成功创建
if (QFile::exists(fileName)) {
QMessageBox::information(nullptr, "导出成功",
QString("Excel文件已成功保存!\n\n"
"文件位置: %1\n"
"文件大小: %2 KB\n"
"数据量: %3 行 × %4 列\n"
"使用的Excel版本: %5")
.arg(fileName)
.arg(QFileInfo(fileName).size() / 1024)
.arg(totalRows)
.arg(totalCols)
.arg(excelVersion));
// 询问是否打开文件
QMessageBox msgBox;
msgBox.setWindowTitle("导出完成");
msgBox.setText("导出已完成,是否要打开生成的Excel文件?");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
if (msgBox.exec() == QMessageBox::Yes) {
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
return true;
} else {
QMessageBox::warning(nullptr, "警告",
"文件保存命令已执行,但目标文件未创建。\n"
"可能是权限不足或路径无效。");
return false;
}
} catch (const std::exception &e) {
// 异常处理
QString errorDetails = QString::fromLocal8Bit(e.what());
// 清理资源
if (worksheet) delete worksheet;
if (workbook) delete workbook;
if (workbooks) delete workbooks;
if (excel) {
try {
excel->dynamicCall("Quit()");
} catch (...) {
// 忽略退出时的异常
}
delete excel;
}
// 显示错误信息
QString errorMsg = "导出过程中发生错误:\n\n";
errorMsg += errorDetails + "\n\n";
errorMsg += "建议:\n";
errorMsg += "1. 确保Excel已正确安装并能正常启动\n";
errorMsg += "2. 尝试使用其他导出格式(CSV或制表符分隔)\n";
errorMsg += "3. 检查是否有足够的磁盘空间和文件权限";
QMessageBox::critical(nullptr, "导出失败", errorMsg);
return false;
} catch (...) {
// 未知异常处理
if (excel) {
try {
excel->dynamicCall("Quit()");
} catch (...) {}
delete excel;
}
QMessageBox::critical(nullptr, "导出失败",
"发生未知错误,无法导出Excel文件。\n"
"建议使用CSV或制表符分隔格式导出。");
return false;
}
}
我的电脑没execl软件,无法演示。。。
优点:
-
生成原生 Excel 文件,支持所有 Excel 特性
-
可控制格式、公式、图表等高级功能
缺点:
-
仅限 Windows 平台
-
依赖 Excel 安装
-
执行效率相对较低
![]()
🎄五、方法四:使用 QtXlsx(跨平台推荐方案)
QtXlsx 是一个用于读写 Excel 文件的第三方 Qt 库,无需安装 Excel,完全跨平台。而且可以带格式的导出。就是操作起来会比前面三种方式繁琐一点。关于具体操作,后面我可能会单独写一篇文章来介绍。
实现代码:
bool saveTableWidgetToExcelWithXlsx(QTableWidget *tableWidget)
{
if (!tableWidget) {
QMessageBox::warning(nullptr, "导出失败", "表格对象为空");
return false;
}
QString fileName = QFileDialog::getSaveFileName(nullptr,
"保存为Excel文件", "data.xlsx", "Excel文件 (*.xlsx)");
if (fileName.isEmpty()) return false;
if (!fileName.endsWith(".xlsx", Qt::CaseInsensitive)) {
fileName += ".xlsx";
}
// 调试信息
qDebug() << "开始导出Excel...";
qDebug() << "表格是否有交替行颜色:" << tableWidget->alternatingRowColors();
qDebug() << "总行数:" << tableWidget->rowCount() << "总列数:" << tableWidget->columnCount();
// 检查几个单元格的颜色
for (int row = 0; row < qMin(3, tableWidget->rowCount()); ++row) {
for (int col = 0; col < qMin(3, tableWidget->columnCount()); ++col) {
QTableWidgetItem *item = tableWidget->item(row, col);
if (item) {
QColor bgColor = item->backgroundColor();
qDebug() << QString("单元格[%1,%2]: 文本='%3', 背景色=%4, 前景色=%5")
.arg(row).arg(col)
.arg(item->text())
.arg(bgColor.name())
.arg(item->foreground().color().name());
}
}
}
// 进度对话框
QProgressDialog progress("正在导出Excel文件...", "取消", 0, tableWidget->rowCount() + 2, nullptr);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(0);
progress.setValue(0);
QApplication::processEvents();
try {
progress.setLabelText("正在初始化...");
progress.setValue(1);
QApplication::processEvents();
QXlsx::Document xlsx;
// 写入表头
progress.setLabelText("正在写入表头...");
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *header = tableWidget->horizontalHeaderItem(col);
QString headerText = header ? header->text() : QString("列%1").arg(col + 1);
QXlsx::Format format;
format.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
format.setVerticalAlignment(QXlsx::Format::AlignVCenter);
format.setBorderStyle(QXlsx::Format::BorderThin);
// 复制表头样式
if (header) {
// 字体
QFont font = header->font();
format.setFontBold(font.bold());
format.setFontItalic(font.italic());
if (font.pointSize() > 0) format.setFontSize(font.pointSize());
if (!font.family().isEmpty()) format.setFontName(font.family());
// 下划线
if (font.underline()) {
format.setFontUnderline(QXlsx::Format::FontUnderlineSingle);
}
// 文字颜色 - 获取表头原来的文字颜色
QColor fgColor = header->foreground().color();
if (fgColor.isValid()) {
format.setFontColor(fgColor);
} else {
// 如果前景色无效,使用默认黑色
format.setFontColor(Qt::black);
}
// 背景颜色 - 获取表头原来的背景颜色
QColor bgColor;
// 方法1:检查background()
QBrush bgBrush = header->background();
if (bgBrush.style() != Qt::NoBrush) {
bgColor = bgBrush.color();
if (bgColor.isValid() && bgColor != Qt::transparent) {
format.setPatternBackgroundColor(bgColor);
}
}
// 方法2:检查通过setData设置的颜色
if (!bgColor.isValid() || bgColor == Qt::transparent) {
QVariant bgData = header->data(Qt::BackgroundRole);
if (bgData.isValid() && bgData.canConvert<QBrush>()) {
QBrush dataBrush = bgData.value<QBrush>();
if (dataBrush.style() != Qt::NoBrush) {
bgColor = dataBrush.color();
if (bgColor.isValid() && bgColor != Qt::transparent) {
format.setPatternBackgroundColor(bgColor);
}
}
}
}
// 方法3:检查textColor/backgroundColor
if (!bgColor.isValid() || bgColor == Qt::transparent) {
bgColor = header->backgroundColor();
if (bgColor.isValid() && bgColor != Qt::transparent) {
format.setPatternBackgroundColor(bgColor);
}
}
// 如果没有找到背景色,设置默认浅灰色
if (!bgColor.isValid() || bgColor == Qt::transparent) {
format.setPatternBackgroundColor(QColor(240, 240, 240));
}
} else {
// 没有表头项,使用默认样式(保持与之前一致)
format.setFontBold(true);
format.setPatternBackgroundColor(QColor(240, 240, 240));
format.setFontColor(Qt::black);
}
xlsx.write(1, col + 1, headerText, format);
}
progress.setValue(2);
QApplication::processEvents();
// 定义交替行颜色
QColor evenRowColor = Qt::white; // 偶数行(从0开始,实际是第1行)
QColor oddRowColor = QColor(240, 240, 240); // 奇数行
// 写入数据行(保持不变)
for (int row = 0; row < tableWidget->rowCount(); ++row) {
progress.setLabelText(QString("正在写入第 %1/%2 行...").arg(row + 1).arg(tableWidget->rowCount()));
for (int col = 0; col < tableWidget->columnCount(); ++col) {
QTableWidgetItem *item = tableWidget->item(row, col);
QString text = item ? item->text() : "";
QXlsx::Format format;
format.setBorderStyle(QXlsx::Format::BorderThin);
// 默认行背景色(处理交替行)
QColor defaultRowBgColor = (row % 2 == 0) ? evenRowColor : oddRowColor;
// 复制单元格样式
if (item) {
// 对齐方式
int alignInt = item->textAlignment();
Qt::Alignment align = static_cast<Qt::Alignment>(alignInt);
// 水平对齐
if (align & Qt::AlignLeft) {
format.setHorizontalAlignment(QXlsx::Format::AlignLeft);
} else if (align & Qt::AlignRight) {
format.setHorizontalAlignment(QXlsx::Format::AlignRight);
} else if (align & Qt::AlignHCenter) {
format.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
} else if (align & Qt::AlignJustify) {
format.setHorizontalAlignment(QXlsx::Format::AlignHJustify);
} else {
// 默认对齐方式:检查是否为数字
bool isNumber = false;
text.toDouble(&isNumber);
if (isNumber && !text.isEmpty()) {
format.setHorizontalAlignment(QXlsx::Format::AlignRight);
} else {
format.setHorizontalAlignment(QXlsx::Format::AlignLeft);
}
}
// 垂直对齐
if (align & Qt::AlignTop) {
format.setVerticalAlignment(QXlsx::Format::AlignTop);
} else if (align & Qt::AlignBottom) {
format.setVerticalAlignment(QXlsx::Format::AlignBottom);
} else if (align & Qt::AlignVCenter) {
format.setVerticalAlignment(QXlsx::Format::AlignVCenter);
} else {
format.setVerticalAlignment(QXlsx::Format::AlignVCenter);
}
// 字体
QFont font = item->font();
format.setFontBold(font.bold());
format.setFontItalic(font.italic());
// 下划线
if (font.underline()) {
format.setFontUnderline(QXlsx::Format::FontUnderlineSingle);
}
// 删除线
if (font.strikeOut()) {
format.setFontStrikeOut(true);
}
if (font.pointSize() > 0) {
format.setFontSize(font.pointSize());
}
if (!font.family().isEmpty()) {
format.setFontName(font.family());
}
// 文字颜色 - 正确获取方式
QColor fgColor = item->foreground().color();
if (fgColor.isValid() && fgColor != Qt::black) {
format.setFontColor(fgColor);
}
// 背景颜色 - 关键修复
QColor bgColor;
// 方法1:检查是否有显式设置的背景色
bool hasExplicitBgColor = false;
// 检查 background() 方法
QBrush bgBrush = item->background();
if (bgBrush.style() != Qt::NoBrush) {
bgColor = bgBrush.color();
if (bgColor.isValid() && bgColor != Qt::transparent) {
hasExplicitBgColor = true;
qDebug() << QString("单元格[%1,%2]使用background()背景色: %3")
.arg(row).arg(col).arg(bgColor.name());
}
}
// 方法2:检查通过setData设置的颜色
if (!hasExplicitBgColor) {
QVariant bgData = item->data(Qt::BackgroundRole);
if (bgData.isValid() && bgData.canConvert<QBrush>()) {
QBrush dataBrush = bgData.value<QBrush>();
if (dataBrush.style() != Qt::NoBrush) {
bgColor = dataBrush.color();
if (bgColor.isValid() && bgColor != Qt::transparent) {
hasExplicitBgColor = true;
qDebug() << QString("单元格[%1,%2]使用data()背景色: %3")
.arg(row).arg(col).arg(bgColor.name());
}
}
}
}
// 方法3:使用setBackgroundColor设置的颜色
if (!hasExplicitBgColor) {
bgColor = item->backgroundColor();
if (bgColor.isValid() && bgColor != Qt::transparent) {
hasExplicitBgColor = true;
qDebug() << QString("单元格[%1,%2]使用backgroundColor(): %3")
.arg(row).arg(col).arg(bgColor.name());
}
}
// 设置背景色
if (hasExplicitBgColor) {
// 使用显式设置的背景色
format.setPatternBackgroundColor(bgColor);
} else {
// 没有显式设置背景色,使用交替行颜色
format.setPatternBackgroundColor(defaultRowBgColor);
}
// 数字格式
bool isNumber = false;
text.toDouble(&isNumber);
if (isNumber && !text.isEmpty()) {
// 检查小数位数
if (text.contains('.')) {
int decimalPlaces = text.length() - text.indexOf('.') - 1;
if (decimalPlaces == 0) {
format.setNumberFormat("0");
} else if (decimalPlaces == 1) {
format.setNumberFormat("0.0");
} else if (decimalPlaces == 2) {
format.setNumberFormat("0.00");
} else {
format.setNumberFormat("0.000");
}
} else {
format.setNumberFormat("0");
}
}
} else {
// 单元格没有item,使用默认样式
format.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
format.setVerticalAlignment(QXlsx::Format::AlignVCenter);
format.setPatternBackgroundColor(defaultRowBgColor);
}
xlsx.write(row + 2, col + 1, text, format);
}
progress.setValue(row + 3);
QApplication::processEvents();
if (progress.wasCanceled()) {
QMessageBox::information(nullptr, "导出取消", "用户取消了导出操作");
return false;
}
}
// 设置列宽
progress.setLabelText("正在调整列宽...");
for (int col = 0; col < tableWidget->columnCount(); ++col) {
int width = tableWidget->columnWidth(col);
if (width > 0) {
int excelWidth = qMax(5, width / 7);
xlsx.setColumnWidth(col + 1, excelWidth);
} else {
xlsx.setColumnWidth(col + 1, 15);
}
}
// 设置行高
progress.setLabelText("正在调整行高...");
for (int row = 0; row < tableWidget->rowCount(); ++row) {
int height = tableWidget->rowHeight(row);
if (height > 0) {
int excelHeight = qMax(15, static_cast<int>(height * 0.75));
xlsx.setRowHeight(row + 2, excelHeight);
}
}
// 保存文件
progress.setLabelText("正在保存文件...");
progress.setValue(tableWidget->rowCount() + 2);
if (xlsx.saveAs(fileName)) {
qDebug() << "Excel文件保存成功:" << fileName;
// 询问是否打开文件
QMessageBox msgBox;
msgBox.setWindowTitle("导出成功");
msgBox.setText(QString("Excel文件已保存:\n%1\n\n"
"总计: %2 行 × %3 列\n"
"表格样式已复制到Excel。")
.arg(fileName)
.arg(tableWidget->rowCount())
.arg(tableWidget->columnCount()));
msgBox.setStandardButtons(QMessageBox::Open | QMessageBox::Ok);
msgBox.setButtonText(QMessageBox::Open, "打开文件");
if (msgBox.exec() == QMessageBox::Open) {
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
}
return true;
} else{
// 简化的详细错误提示
QFile file(fileName);
QString errorMsg;
if (file.exists()) {
// 检查文件是否可写/被占用
if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) {
errorMsg = QString("保存失败: 文件被其他程序占用\n\n"
"文件: %1\n"
"错误: %2\n\n"
"请关闭正在使用此文件的程序(如Excel),然后重试。\n"
"或者换个文件名保存。")
.arg(fileName)
.arg(file.errorString());
} else {
file.close();
errorMsg = QString("保存失败: 无法覆盖文件\n\n文件: %1").arg(fileName);
}
} else {
// 文件不存在,检查目录权限
QFileInfo fileInfo(fileName);
QDir dir = fileInfo.absoluteDir();
if (!dir.exists()) {
errorMsg = QString("保存失败: 目录不存在\n\n目录: %1").arg(dir.path());
} else {
// 检查目录权限
QFileInfo dirInfo(dir.path());
if (!dirInfo.isWritable()) {
errorMsg = QString("保存失败: 没有写入权限\n\n目录: %1").arg(dir.path());
} else {
errorMsg = QString("保存失败: 无法创建文件\n\n文件: %1").arg(fileName);
}
}
}
throw std::runtime_error(errorMsg.toStdString());
}
} catch (const std::exception &e) {
qDebug() << "导出错误:" << e.what();
QMessageBox::critical(nullptr, "导出失败",
QString("导出过程中发生错误:\n%1").arg(e.what()));
return false;
} catch (...) {
qDebug() << "导出未知错误";
QMessageBox::critical(nullptr, "导出失败", "导出过程中发生未知错误");
return false;
}
}
运行结果:

优点:
-
真正跨平台,不依赖 Excel
-
生成原生 .xlsx 格式文件
-
功能丰富,支持单元格格式、公式等
![]()
🎄六、总结
本文详细介绍了四种将 QTableWidget 数据导出到 Excel 的实用方法,同时提供了代码、结果演示,可以根据项目实际需求选择合适的方案。所有方法均已封装成独立函数,只需传入 QTableWidget 对象即可调用,大大简化了开发工作。对于大型数据导出(超过 1000 行),建议添加进度提示和取消功能,避免界面假死。
| 特性对比 | CSV 格式 | 制表符分隔 | QAxObject | QtXlsx |
|---|---|---|---|---|
| 跨平台 | ✅ 全平台 | ✅ 全平台 | ❌ 仅 Windows | ✅ 全平台 |
| Excel 依赖 | 无需 | 无需 | 需要安装 | 无需 |
| 文件格式 | .csv 文本 | .xls/.xlsx(文本) | 原生 .xlsx | 原生 .xlsx |
| 执行效率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 功能完整性 | 基础数据 | 基础数据 | 完整功能 | 较完整功能 |
| 实现复杂度 | 简单 | 简单 | 中等 | 中等 |
| 推荐场景 | 快速导出、兼容性要求高 | Excel 打开体验好 | Windows 专属、需要高级功能 | 跨平台、需要原生格式 |

如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁
5931



被折叠的 条评论
为什么被折叠?



