资讯动态

Qt C++中使用QTableWidget与QXlsx实现Excel文件高效读写

发布时间:2026/9/6 2:56:33 来源:尧图企业网站定制
在日常的桌面应用开发中处理Excel文件是一个高频且关键的需求。无论是数据报表生成、批量数据导入导出还是与现有业务系统进行数据交互Excel都扮演着重要角色。然而直接在C层面处理Excel文件格式复杂、容易出错而借助Qt框架提供的表格控件我们可以用更直观、高效的方式完成这些任务。本文将详细讲解如何在Qt C环境中利用QTableWidget等表格控件实现Excel文件的读取、显示、编辑和保存。内容涵盖从基础的环境搭建、核心控件使用到完整的数据处理流程并针对大文件处理、格式兼容性等常见痛点提供解决方案。无论你是刚接触Qt的新手还是希望优化现有数据处理逻辑的开发者都能从中获得实用的代码示例和工程经验。1. Qt表格控件与Excel处理基础1.1 Qt表格控件简介Qt提供了多种用于表格数据展示和编辑的控件其中最常用的是QTableWidget。它是一个基于项item的表格视图内置了数据模型适合处理中等规模的结构化数据。与需要自定义模型的QTableView相比QTableWidget更易于上手能够快速实现数据的增删改查。除了显示功能QTableWidget还支持单元格格式设置、排序、拖拽等交互特性这些功能与Excel的操作习惯高度契合使得用户能够以熟悉的方式处理数据。1.2 Excel文件处理方案对比在Qt中处理Excel文件主要有以下几种方案使用QAxObjectWindows平台通过Qt的ActiveX支持直接操作Excel应用程序功能最完整但仅限Windows环境且依赖安装Office软件。使用第三方库如QXlsx纯C库跨平台支持好不依赖Office软件可以读写xlsx格式文件是目前的主流选择。CSV格式中转将Excel文件另存为CSV格式进行处理简单快速但会丢失格式、公式等多表页信息。考虑到跨平台需求和功能完整性本文将重点介绍基于QXlsx的方案它在功能性和易用性之间取得了良好平衡。1.3 开发环境准备在开始编码前需要确保开发环境正确配置Qt版本Qt 5.15或Qt 6.2及以上版本本文示例基于Qt 6.5编译器MSVC 2019、MinGW或GCC等支持C17的编译器第三方库QXlsxGitHub开源库QXlsx可以通过源码集成到项目中具体安装步骤将在下一节详细说明。2. 环境搭建与QXlsx集成2.1 下载和配置QXlsxQXlsx是一个用于读写Excel xlsx文件的Qt库支持单元格格式、公式、图表等特性。配置步骤如下首先从GitHub仓库下载源码git clone https://github.com/QtExcel/QXlsx.git然后将QXlsx作为子模块或直接源码集成到项目中。在Qt项目的.pro文件中添加# 假设QXlsx源码放在项目根目录的thirdparty/QXlsx文件夹下 include($$PWD/thirdparty/QXlsx/QXlsx.pri) # 或者直接引用源码文件 SOURCES $$PWD/thirdparty/QXlsx/source/*.cpp HEADERS $$PWD/thirdparty/QXlsx/header/*.h2.2 基础项目配置创建基本的Qt Widgets Application项目在mainwindow.h中添加必要的头文件引用#ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QTableWidget #include QFileDialog #include QMessageBox #include xlsxdocument.h #include xlsxformat.h QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); private slots: void onOpenFile(); void onSaveFile(); private: Ui::MainWindow *ui; QXlsx::Document *xlsxDoc; }; #endif // MAINWINDOW_H2.3 界面布局设计在Qt Designer中设计主界面包含以下核心组件QTableWidget用于显示和编辑Excel数据工具栏按钮打开文件、保存文件、新增行、删除行等状态栏显示当前文件信息和操作状态界面布局可以采用经典的工具栏中心表格状态栏结构确保操作便捷性和信息可见性。3. QTableWidget核心功能详解3.1 表格初始化与基础设置正确初始化表格控件是确保功能正常的基础。在MainWindow的构造函数中完成表格的初始化MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , xlsxDoc(nullptr) { ui-setupUi(this); // 初始化表格属性 ui-tableWidget-setAlternatingRowColors(true); // 交替行颜色 ui-tableWidget-setSelectionMode(QAbstractItemView::SingleSelection); ui-tableWidget-setSelectionBehavior(QAbstractItemView::SelectItems); ui-tableWidget-horizontalHeader()-setStretchLastSection(true); // 设置列数自适应内容 ui-tableWidget-horizontalHeader()-setSectionResizeMode(QHeaderView::Interactive); // 连接信号槽 connect(ui-actionOpen, QAction::triggered, this, MainWindow::onOpenFile); connect(ui-actionSave, QAction::triggered, this, MainWindow::onSaveFile); }3.2 数据加载与显示将Excel数据加载到QTableWidget需要处理行列信息转换和数据类型识别void MainWindow::loadExcelToTable(const QString fileName) { if (xlsxDoc) { delete xlsxDoc; } xlsxDoc new QXlsx::Document(fileName); if (!xlsxDoc-load()) { QMessageBox::critical(this, 错误, 无法打开Excel文件); return; } // 获取工作表数量和信息 QStringList sheetNames xlsxDoc-sheetNames(); if (sheetNames.isEmpty()) { QMessageBox::information(this, 提示, Excel文件为空); return; } // 默认加载第一个工作表 xlsxDoc-selectSheet(sheetNames.first()); // 获取数据范围 int rowCount xlsxDoc-dimension().rowCount(); int colCount xlsxDoc-dimension().columnCount(); // 设置表格行列数 ui-tableWidget-setRowCount(rowCount); ui-tableWidget-setColumnCount(colCount); // 加载数据 for (int row 1; row rowCount; row) { for (int col 1; col colCount; col) { QXlsx::Cell *cell xlsxDoc-cellAt(row, col); if (cell) { QVariant value cell-value(); QTableWidgetItem *item new QTableWidgetItem(value.toString()); // 根据数据类型设置不同的对齐方式 if (value.type() QVariant::Double || value.type() QVariant::Int || value.type() QVariant::LongLong) { item-setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); } else { item-setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); } ui-tableWidget-setItem(row - 1, col - 1, item); } } } // 设置表头尝试读取第一行作为表头 if (rowCount 0) { QStringList headers; for (int col 1; col colCount; col) { QXlsx::Cell *cell xlsxDoc-cellAt(1, col); headers (cell ? cell-value().toString() : QString(列%1).arg(col)); } ui-tableWidget-setHorizontalHeaderLabels(headers); } statusBar()-showMessage(QString(已加载: %1 (共%2行%3列)).arg(fileName).arg(rowCount).arg(colCount)); }3.3 单元格格式与数据验证为了提供更好的用户体验需要实现数据类型验证和格式设置// 设置数值型单元格的输入验证 void MainWindow::setupCellValidators() { for (int row 0; row ui-tableWidget-rowCount(); row) { for (int col 0; col ui-tableWidget-columnCount(); col) { QTableWidgetItem *item ui-tableWidget-item(row, col); if (item) { // 根据表头判断数据类型简单实现 QString header ui-tableWidget-horizontalHeaderItem(col)-text(); if (header.contains(金额) || header.contains(价格) || header.contains(数量) || header.contains(比率)) { // 设置数值验证器 QDoubleValidator *validator new QDoubleValidator(this); validator-setDecimals(2); // 两位小数 // 可以通过item的setData存储验证器信息 } } } } } // 自定义委托用于特定格式输入 class NumericItemDelegate : public QItemDelegate { public: QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem option, const QModelIndex index) const override { QLineEdit *editor new QLineEdit(parent); QDoubleValidator *validator new QDoubleValidator(editor); validator-setDecimals(2); editor-setValidator(validator); return editor; } };4. Excel文件读写完整实现4.1 读取Excel文件详细流程Excel文件读取需要考虑多种边界情况和性能优化void MainWindow::onOpenFile() { QString fileName QFileDialog::getOpenFileName(this, 打开Excel文件, , Excel文件 (*.xlsx *.xls)); if (fileName.isEmpty()) { return; } QApplication::setOverrideCursor(Qt::WaitCursor); try { // 清空现有数据 ui-tableWidget-clearContents(); ui-tableWidget-setRowCount(0); // 创建文档对象 QXlsx::Document document(fileName); if (!document.load()) { throw std::runtime_error(文件格式错误或损坏); } // 处理多工作表 QStringList sheets document.sheetNames(); if (sheets.size() 1) { // 提供工作表选择对话框 bool ok; QString sheetName QInputDialog::getItem(this, 选择工作表, 请选择要加载的工作表:, sheets, 0, false, ok); if (!ok) return; document.selectSheet(sheetName); } else { document.selectSheet(sheets.first()); } // 获取数据范围 QXlsx::CellRange range document.dimension(); if (!range.isValid()) { throw std::runtime_error(工作表为空); } int rowCount range.rowCount(); int colCount range.columnCount(); // 性能优化分批加载大数据量 const int BATCH_SIZE 1000; ui-tableWidget-setUpdatesEnabled(false); ui-tableWidget-setRowCount(rowCount); ui-tableWidget-setColumnCount(colCount); // 分批加载数据 for (int startRow 1; startRow rowCount; startRow BATCH_SIZE) { int endRow qMin(startRow BATCH_SIZE - 1, rowCount); for (int row startRow; row endRow; row) { for (int col 1; col colCount; col) { QXlsx::Cell *cell document.cellAt(row, col); if (cell) { QTableWidgetItem *item createTableItemFromCell(cell); ui-tableWidget-setItem(row - 1, col - 1, item); } } // 每100行更新一次进度 if (row % 100 0) { QApplication::processEvents(); } } } ui-tableWidget-setUpdatesEnabled(true); // 设置表头 setupTableHeaders(document, colCount); statusBar()-showMessage(QString(成功加载: %1 (%2行 × %3列)) .arg(QFileInfo(fileName).fileName()) .arg(rowCount).arg(colCount)); } catch (const std::exception e) { QMessageBox::critical(this, 读取错误, e.what()); } QApplication::restoreOverrideCursor(); } QTableWidgetItem* MainWindow::createTableItemFromCell(QXlsx::Cell *cell) { QVariant value cell-value(); QTableWidgetItem *item new QTableWidgetItem(); // 处理不同类型的数据 switch (value.type()) { case QVariant::Double: item-setData(Qt::DisplayRole, value.toDouble()); item-setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); break; case QVariant::Int: case QVariant::LongLong: item-setData(Qt::DisplayRole, value.toLongLong()); item-setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); break; case QVariant::Date: item-setData(Qt::DisplayRole, value.toDate().toString(yyyy-MM-dd)); break; case QVariant::DateTime: item-setData(Qt::DisplayRole, value.toDateTime().toString(yyyy-MM-dd hh:mm:ss)); break; default: item-setText(value.toString()); item-setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); } // 保留原始单元格格式信息 item-setData(Qt::UserRole, QVariant::fromValue(cell)); return item; }4.2 保存数据到Excel文件将QTableWidget中的数据保存回Excel文件需要处理格式保持和数据完整性void MainWindow::onSaveFile() { if (ui-tableWidget-rowCount() 0) { QMessageBox::information(this, 提示, 没有数据需要保存); return; } QString fileName QFileDialog::getSaveFileName(this, 保存Excel文件, , Excel文件 (*.xlsx)); if (fileName.isEmpty()) { return; } // 确保文件扩展名 if (!fileName.endsWith(.xlsx, Qt::CaseInsensitive)) { fileName .xlsx; } QApplication::setOverrideCursor(Qt::WaitCursor); try { QXlsx::Document document; int rowCount ui-tableWidget-rowCount(); int colCount ui-tableWidget-columnCount(); // 写入表头 for (int col 0; col colCount; col) { QTableWidgetItem *headerItem ui-tableWidget-horizontalHeaderItem(col); if (headerItem) { document.write(1, col 1, headerItem-text()); } } // 写入数据 for (int row 0; row rowCount; row) { for (int col 0; col colCount; col) { QTableWidgetItem *item ui-tableWidget-item(row, col); if (item) { // 尝试恢复原始单元格格式 QVariant cellData item-data(Qt::UserRole); if (cellData.canConvertQXlsx::Cell*()) { QXlsx::Cell *originalCell cellData.valueQXlsx::Cell*(); if (originalCell) { // 复制原始格式 QXlsx::Format format originalCell-format(); document.write(row 2, col 1, item-text(), format); continue; } } // 新数据根据内容类型设置格式 QVariant::Type dataType item-data(Qt::DisplayRole).type(); if (dataType QVariant::Double || dataType QVariant::Int) { QXlsx::Format format; format.setNumberFormat(#,##0.00); document.write(row 2, col 1, item-data(Qt::DisplayRole).toDouble(), format); } else { document.write(row 2, col 1, item-text()); } } } // 进度更新 if (row % 100 0) { QApplication::processEvents(); } } // 自动调整列宽 for (int col 1; col colCount; col) { document.setColumnWidth(col, 15); // 设置默认列宽 } if (!document.saveAs(fileName)) { throw std::runtime_error(文件保存失败请检查路径和权限); } statusBar()-showMessage(QString(已保存: %1).arg(fileName)); } catch (const std::exception e) { QMessageBox::critical(this, 保存错误, e.what()); } QApplication::restoreOverrideCursor(); }4.3 大文件处理优化策略处理大型Excel文件时需要特别注意内存使用和响应性能// 增量加载大文件 void MainWindow::loadLargeExcelIncremental(const QString fileName) { QXlsx::Document document(fileName); if (!document.load()) { return; } QXlsx::CellRange range document.dimension(); int totalRows range.rowCount(); // 设置虚拟行数实现滚动加载 ui-tableWidget-setRowCount(totalRows); // 只加载可见区域的数据 connect(ui-tableWidget-verticalScrollBar(), QScrollBar::valueChanged, this, [this, document, totalRows](int value) { loadVisibleRows(document, value, totalRows); }); // 初始加载前1000行 loadVisibleRows(document, 0, totalRows); } void MainWindow::loadVisibleRows(QXlsx::Document document, int scrollValue, int totalRows) { int visibleRowCount ui-tableWidget-viewport()-height() / ui-tableWidget-rowHeight(0); int startRow scrollValue; int endRow qMin(startRow visibleRowCount * 2, totalRows); // 预加载一些行 ui-tableWidget-setUpdatesEnabled(false); for (int row startRow; row endRow; row) { if (!ui-tableWidget-item(row, 0)) { // 如果该行尚未加载 for (int col 1; col ui-tableWidget-columnCount(); col) { QXlsx::Cell *cell document.cellAt(row 1, col); if (cell) { QTableWidgetItem *item createTableItemFromCell(cell); ui-tableWidget-setItem(row, col - 1, item); } } } } ui-tableWidget-setUpdatesEnabled(true); }5. 高级功能与用户体验优化5.1 数据筛选与搜索实现类似Excel的数据筛选功能提升数据查找效率// 实现简单的表格筛选 void MainWindow::setupTableFilter() { // 为每列添加筛选框 for (int col 0; col ui-tableWidget-columnCount(); col) { QLineEdit *filterEdit new QLineEdit(this); filterEdit-setPlaceholderText(筛选...); filterEdit-setClearButtonEnabled(true); connect(filterEdit, QLineEdit::textChanged, this, [this, col](const QString text) { applyColumnFilter(col, text); }); ui-tableWidget-setCellWidget(0, col, filterEdit); // 在第一行显示筛选框 } } void MainWindow::applyColumnFilter(int column, const QString filterText) { for (int row 1; row ui-tableWidget-rowCount(); row) { QTableWidgetItem *item ui-tableWidget-item(row, column); bool shouldShow filterText.isEmpty() || (item item-text().contains(filterText, Qt::CaseInsensitive)); ui-tableWidget-setRowHidden(row, !shouldShow); } }5.2 数据验证与完整性检查确保输入数据的正确性和完整性class DataValidator { public: static bool validateEmail(const QString email) { QRegularExpression regex(R(\b[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Z|a-z]{2,}\b)); return regex.match(email).hasMatch(); } static bool validatePhone(const QString phone) { QRegularExpression regex(R(^1[3-9]\d{9}$)); return regex.match(phone).hasMatch(); } static bool validateNumberRange(double value, double min, double max) { return value min value max; } }; // 在数据保存前进行验证 bool MainWindow::validateDataBeforeSave() { QStringList errors; for (int row 0; row ui-tableWidget-rowCount(); row) { for (int col 0; col ui-tableWidget-columnCount(); col) { QTableWidgetItem *item ui-tableWidget-item(row, col); if (!item || item-text().isEmpty()) { continue; } QString header ui-tableWidget-horizontalHeaderItem(col)-text(); QString value item-text(); // 根据列名进行相应的验证 if (header.contains(邮箱) || header.contains(Email)) { if (!DataValidator::validateEmail(value)) { errors QString(第%1行邮箱格式错误: %2).arg(row 1).arg(value); } } else if (header.contains(电话) || header.contains(手机)) { if (!DataValidator::validatePhone(value)) { errors QString(第%1行手机号格式错误: %2).arg(row 1).arg(value); } } else if (header.contains(年龄) || header.contains(数量)) { bool ok; double num value.toDouble(ok); if (!ok || !DataValidator::validateNumberRange(num, 0, 150)) { errors QString(第%1行数值范围错误: %2).arg(row 1).arg(value); } } } } if (!errors.isEmpty()) { QMessageBox::warning(this, 数据验证错误, errors.join(\n)); return false; } return true; }5.3 撤销重做功能实现为表格编辑提供撤销重做支持提升用户体验class TableEditCommand : public QUndoCommand { public: TableEditCommand(QTableWidget *table, int row, int col, const QString oldValue, const QString newValue) : m_table(table), m_row(row), m_col(col), m_oldValue(oldValue), m_newValue(newValue) {} void undo() override { if (m_table-item(m_row, m_col)) { m_table-item(m_row, m_col)-setText(m_oldValue); } } void redo() override { if (m_table-item(m_row, m_col)) { m_table-item(m_row, m_col)-setText(m_newValue); } } private: QTableWidget *m_table; int m_row, m_col; QString m_oldValue, m_newValue; }; // 在MainWindow中集成撤销重做栈 void MainWindow::setupUndoRedo() { m_undoStack new QUndoStack(this); // 监听表格内容变化 connect(ui-tableWidget, QTableWidget::itemChanged, this, MainWindow::onTableItemChanged); } void MainWindow::onTableItemChanged(QTableWidgetItem *item) { if (m_lastItem m_lastItem item m_lastValue ! item-text()) { TableEditCommand *command new TableEditCommand( ui-tableWidget, item-row(), item-column(), m_lastValue, item-text()); m_undoStack-push(command); } m_lastItem item; m_lastValue item-text(); }6. 性能优化与内存管理6.1 大数据量处理优化当处理数万行以上的Excel文件时需要采用特殊优化策略// 使用后台线程加载数据 class ExcelLoader : public QObject { Q_OBJECT public: ExcelLoader(const QString fileName, QObject *parent nullptr) : QObject(parent), m_fileName(fileName) {} public slots: void load() { try { QXlsx::Document document(m_fileName); if (!document.load()) { emit error(无法加载文件); return; } QXlsx::CellRange range document.dimension(); int rowCount range.rowCount(); int colCount range.columnCount(); // 分批发送数据到主线程 const int BATCH_SIZE 500; QVectorQVectorQVariant batchData; for (int row 1; row rowCount; row) { QVectorQVariant rowData; for (int col 1; col colCount; col) { QXlsx::Cell *cell document.cellAt(row, col); rowData (cell ? cell-value() : QVariant()); } batchData rowData; if (batchData.size() BATCH_SIZE) { emit dataReady(batchData, row); batchData.clear(); QThread::msleep(10); // 让出CPU避免界面卡顿 } } if (!batchData.isEmpty()) { emit dataReady(batchData, rowCount); } emit finished(); } catch (const std::exception e) { emit error(e.what()); } } signals: void dataReady(const QVectorQVectorQVariant data, int currentRow); void finished(); void error(const QString message); private: QString m_fileName; }; // 在主线程中接收数据 void MainWindow::setupBackgroundLoading(const QString fileName) { QThread *thread new QThread(this); ExcelLoader *loader new ExcelLoader(fileName); loader-moveToThread(thread); connect(thread, QThread::started, loader, ExcelLoader::load); connect(loader, ExcelLoader::dataReady, this, MainWindow::onDataChunkReady); connect(loader, ExcelLoader::finished, thread, QThread::quit); connect(loader, ExcelLoader::finished, loader, ExcelLoader::deleteLater); connect(thread, QThread::finished, thread, QThread::deleteLater); connect(loader, ExcelLoader::error, this, MainWindow::onLoadError); thread-start(); }6.2 内存使用监控与优化实时监控内存使用防止内存泄漏class MemoryMonitor : public QObject { Q_OBJECT public: static qint64 getMemoryUsage() { #ifdef Q_OS_WIN PROCESS_MEMORY_COUNTERS pmc; if (GetProcessMemoryInfo(GetCurrentProcess(), pmc, sizeof(pmc))) { return pmc.WorkingSetSize; } #elif defined(Q_OS_LINUX) QFile file(/proc/self/status); if (file.open(QIODevice::ReadOnly)) { QTextStream in(file); while (!in.atEnd()) { QString line in.readLine(); if (line.startsWith(VmRSS:)) { QStringList parts line.split( , Qt::SkipEmptyParts); if (parts.size() 2) { return parts[1].toLongLong() * 1024; // KB to bytes } } } } #endif return 0; } }; // 定期检查内存使用 void MainWindow::startMemoryMonitoring() { QTimer *timer new QTimer(this); connect(timer, QTimer::timeout, this, [this]() { qint64 memory MemoryMonitor::getMemoryUsage(); if (memory 500 * 1024 * 1024) { // 超过500MB QMessageBox::warning(this, 内存警告, 内存使用过高建议保存数据后重启应用); } }); timer-start(5000); // 每5秒检查一次 }7. 常见问题与解决方案7.1 文件格式兼容性问题不同版本的Excel文件可能存在兼容性问题需要特殊处理QString MainWindow::detectFileFormat(const QString fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { return 未知格式; } QByteArray header file.read(8); file.close(); if (header.startsWith(\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1)) { return xls (旧格式); } else if (header.startsWith(PK\x03\x04)) { return xlsx (新格式); } else { return 未知格式; } } // 处理旧版xls文件 bool MainWindow::convertXlsToXlsx(const QString xlsFile, const QString xlsxFile) { // 可以使用libxls等第三方库进行转换 // 或者提示用户手动转换 QMessageBox::information(this, 格式转换, 检测到旧版xls格式文件建议使用Excel另存为xlsx格式); return false; }7.2 中文编码问题处理确保中文字符正确显示和保存// 设置正确的文本编码 void MainWindow::setupTextEncoding() { QTextCodec *codec QTextCodec::codecForName(UTF-8); QTextCodec::setCodecForLocale(codec); } // 处理可能的编码问题 QString MainWindow::fixEncoding(const QString text) { // 检测常见的编码问题并修复 if (text.contains() || text.contains(�)) { // 尝试多种编码转换 QByteArray latin1 text.toLatin1(); QString utf8 QString::fromUtf8(latin1); if (!utf8.contains()) { return utf8; } } return text; }7.3 性能问题排查清单当遇到性能问题时可以按以下清单排查文件大小检查确认文件是否过大50MB考虑分批处理内存使用监控检查内存是否泄漏使用任务管理器监控UI响应测试在数据加载时测试界面响应性磁盘IO分析检查磁盘读写速度避免同时进行大量IO操作第三方库版本确保使用最新版本的QXlsx修复已知性能问题8. 最佳实践与工程建议8.1 代码组织与架构设计良好的代码结构是维护性的基础// 使用MVC模式分离数据逻辑和界面逻辑 class ExcelDataModel : public QObject { Q_OBJECT public: explicit ExcelDataModel(QObject *parent nullptr); bool loadFile(const QString fileName); bool saveFile(const QString fileName); QVariant data(int row, int col) const; bool setData(int row, int col, const QVariant value); int rowCount() const { return m_data.size(); } int columnCount() const { return m_data.isEmpty() ? 0 : m_data.first().size(); } signals: void dataChanged(int row, int col); void loadProgress(int percent); private: QVectorQVectorQVariant m_data; }; // 表格视图控制器 class TableViewController : public QObject { Q_OBJECT public: TableViewController(QTableWidget *view, ExcelDataModel *model, QObject *parent nullptr); private slots: void onModelDataChanged(int row, int col); void onViewItemChanged(QTableWidgetItem *item); private: QTableWidget *m_view; ExcelDataModel *m_model; };8.2 错误处理与日志记录完善的错误处理机制是生产环境应用的必备特性class ApplicationLogger : public QObject { public: static void init() { qInstallMessageHandler(ApplicationLogger::messageHandler); } static void messageHandler(QtMsgType type, const QMessageLogContext context, const QString msg) { QString logMessage QString([%1] %2 (%3:%4)).arg( QDateTime::currentDateTime().toString(yyyy-MM-dd hh:mm:ss.zzz), msg, context.file, QString::number(context.line)); // 输出到控制台 fprintf(stderr, %s\n, logMessage.toLocal8Bit().constData()); // 写入文件 QFile logFile(application.log); if (logFile.open(QIODevice::Append | QIODevice::Text)) { QTextStream stream(logFile); stream logMessage \n; } } }; // 在main函数中初始化 int main(int argc, char *argv[]) { ApplicationLogger::init(); QApplication app(argc, argv); // ... 应用初始化 }8.3 测试策略与质量保证确保代码质量的测试方案// 单元测试示例 class ExcelProcessorTest : public QObject { Q_OBJECT private slots: void testLoadValidFile() { ExcelProcessor processor; QVERIFY(processor.loadFile(test_data.xlsx)); QCOMPARE(processor.rowCount(), 100); } void testSaveFile() { ExcelProcessor processor; processor.loadFile(test_data.xlsx); QVERIFY(processor.saveFile(test_output.xlsx)); // 验证保存的文件内容 ExcelProcessor verifyProcessor; QVERIFY(verifyProcessor.loadFile(test_output.xlsx)); QCOMPARE(verifyProcessor.rowCount(), 100); } void testDataValidation() { ExcelProcessor processor; processor.loadFile(test_data.xlsx); // 测试特定数据验证规则

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价