Qt C++ Excel 文件解析与导出实战:QAxObject 封装工具类

一、前言

在Qt C++开发中,Excel文件的解析与导出是极为常见的业务需求,如报表生成、数据批量处理、配置文件读写等场景。Qt本身未提供原生的Excel操作API,而通过QAxObject调用COM组件(Microsoft Excel Object Library)是Windows平台下实现Excel操作的主流方案。本文将从实战角度出发,详细讲解如何封装一个高复用、易扩展的Excel操作工具类,涵盖Excel文件的创建、数据读取、数据写入、格式设置、工作表管理等核心功能,并结合实际案例演示使用方式,全文约5000字。

二、技术基础与环境准备

2.1 QAxObject 核心原理

QAxObject是Qt ActiveQt模块提供的核心类,用于与COM组件交互。Excel作为COM服务器,暴露了完整的对象模型(Application、Workbook、Worksheet、Range等),QAxObject通过封装这些COM对象,实现对Excel的程序化控制。

2.2 环境依赖

  • 操作系统:Windows(COM组件仅支持Windows平台);
  • Qt版本:Qt 5.x/6.x(需启用ActiveQt模块);
  • Excel环境:安装Microsoft Excel(2007及以上版本);
  • 编译配置 :Qt工程文件(.pro)中添加QT += axcontainer,启用ActiveQt模块。

2.3 核心Excel COM对象层级

复制代码
Excel.Application(应用程序对象)
└── Workbooks(工作簿集合)
    └── Workbook(工作簿对象)
        └── Worksheets(工作表集合)
            └── Worksheet(工作表对象)
                └── Range(单元格/单元格区域对象)

三、Excel工具类封装设计

3.1 类结构设计

封装ExcelHelper类,采用单例模式(或普通类),对外提供简洁的API,内部处理COM对象的创建、释放、异常捕获等细节。核心功能包括:

  1. Excel应用程序初始化与销毁;
  2. 工作簿的创建、打开、保存、关闭;
  3. 工作表的创建、删除、重命名、切换;
  4. 单元格数据的读取(文本、数字、日期);
  5. 单元格数据的写入(文本、数字、日期);
  6. 单元格格式设置(字体、颜色、对齐方式);
  7. 批量数据读写(提升大文件操作效率);
  8. 异常处理与日志输出。

3.2 头文件定义(ExcelHelper.h)

cpp 复制代码
#ifndef EXCELHELPER_H
#define EXCELHELPER_H

#include <QAxObject>
#include <QString>
#include <QVariant>
#include <QColor>
#include <QFont>
#include <QVector>
#include <QMap>

// Excel操作工具类
class ExcelHelper
{
public:
    // 单例获取
    static ExcelHelper& instance();

    // 初始化Excel应用(隐藏Excel窗口)
    bool initExcel();

    // 释放Excel资源(关闭所有工作簿,退出应用)
    void releaseExcel();

    // ========== 工作簿操作 ==========
    // 创建新工作簿
    bool createWorkbook();

    // 打开已存在的工作簿
    bool openWorkbook(const QString& filePath);

    // 保存工作簿(path为空则保存到当前路径)
    bool saveWorkbook(const QString& path = "");

    // 另存为(支持xlsx/xls格式)
    bool saveWorkbookAs(const QString& filePath);

    // 关闭当前工作簿
    void closeWorkbook();

    // ========== 工作表操作 ==========
    // 创建新工作表(指定名称,默认插入到最后)
    bool createWorksheet(const QString& sheetName);

    // 删除指定名称的工作表
    bool deleteWorksheet(const QString& sheetName);

    // 重命名工作表
    bool renameWorksheet(const QString& oldName, const QString& newName);

    // 切换到指定工作表
    bool selectWorksheet(const QString& sheetName);

    // 获取当前工作表名称
    QString currentWorksheetName() const;

    // 获取所有工作表名称列表
    QStringList allWorksheetNames() const;

    // ========== 单元格操作 ==========
    // 读取单元格数据(行、列从1开始)
    QVariant readCell(int row, int col);

    // 写入单元格数据(行、列从1开始)
    bool writeCell(int row, int col, const QVariant& value);

    // 批量读取单元格区域(startRow,startCol -> endRow,endCol)
    QVector<QVector<QVariant>> readRange(int startRow, int startCol, int endRow, int endCol);

    // 批量写入单元格区域
    bool writeRange(int startRow, int startCol, const QVector<QVector<QVariant>>& data);

    // 设置单元格字体
    bool setCellFont(int row, int col, const QFont& font);

    // 设置单元格背景色
    bool setCellBackgroundColor(int row, int col, const QColor& color);

    // 设置单元格对齐方式(水平、垂直)
    bool setCellAlignment(int row, int col, Qt::AlignmentFlag hAlign, Qt::AlignmentFlag vAlign);

    // ========== 错误信息 ==========
    QString lastError() const;

private:
    // 私有构造函数(单例)
    ExcelHelper();
    ~ExcelHelper();

    // 禁用拷贝
    ExcelHelper(const ExcelHelper&) = delete;
    ExcelHelper& operator=(const ExcelHelper&) = delete;

    // 获取单元格区域对象(如"A1"、"B2:C5")
    QAxObject* getRange(const QString& rangeStr);
    QAxObject* getRange(int row, int col);
    QAxObject* getRange(int startRow, int startCol, int endRow, int endCol);

    // 设置错误信息
    void setLastError(const QString& error);

    // COM对象指针
    QAxObject* m_excelApp;       // Excel应用程序
    QAxObject* m_workbook;       // 当前工作簿
    QAxObject* m_worksheet;      // 当前工作表
    QString m_lastError;         // 最后一次错误信息
    QString m_currentFilePath;   // 当前工作簿路径
};

#endif // EXCELHELPER_H

3.3 源文件实现(ExcelHelper.cpp)

cpp 复制代码
#include "ExcelHelper.h"
#include <QDebug>
#include <QFile>
#include <QDateTime>

// 单例实现
ExcelHelper& ExcelHelper::instance()
{
    static ExcelHelper helper;
    return helper;
}

ExcelHelper::ExcelHelper()
    : m_excelApp(nullptr)
    , m_workbook(nullptr)
    , m_worksheet(nullptr)
{
}

ExcelHelper::~ExcelHelper()
{
    releaseExcel();
}

// 初始化Excel应用
bool ExcelHelper::initExcel()
{
    if (m_excelApp) {
        setLastError("Excel应用已初始化");
        return true;
    }

    m_excelApp = new QAxObject("Excel.Application");
    if (!m_excelApp || m_excelApp->isNull()) {
        setLastError("初始化Excel应用失败,请确认已安装Excel");
        delete m_excelApp;
        m_excelApp = nullptr;
        return false;
    }

    // 隐藏Excel窗口,后台操作
    m_excelApp->setProperty("Visible", false);
    // 禁用显示警告对话框(如覆盖文件提示)
    m_excelApp->setProperty("DisplayAlerts", false);

    setLastError("");
    return true;
}

// 释放Excel资源
void ExcelHelper::releaseExcel()
{
    // 关闭当前工作簿
    closeWorkbook();

    // 退出Excel应用
    if (m_excelApp) {
        m_excelApp->dynamicCall("Quit()");
        delete m_excelApp;
        m_excelApp = nullptr;
    }

    m_lastError.clear();
    m_currentFilePath.clear();
}

// 创建新工作簿
bool ExcelHelper::createWorkbook()
{
    if (!m_excelApp) {
        if (!initExcel()) {
            return false;
        }
    }

    // 关闭已有工作簿
    closeWorkbook();

    QAxObject* workbooks = m_excelApp->querySubObject("Workbooks");
    if (!workbooks) {
        setLastError("获取工作簿集合失败");
        return false;
    }

    m_workbook = workbooks->dynamicCall("Add()").value<QAxObject*>();
    delete workbooks;

    if (!m_workbook || m_workbook->isNull()) {
        setLastError("创建新工作簿失败");
        return false;
    }

    // 默认选中第一个工作表
    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    if (worksheets) {
        m_worksheet = worksheets->querySubObject("Item(int)", 1);
        delete worksheets;
    }

    setLastError("");
    return true;
}

// 打开已存在的工作簿
bool ExcelHelper::openWorkbook(const QString& filePath)
{
    if (!QFile::exists(filePath)) {
        setLastError(QString("文件不存在:%1").arg(filePath));
        return false;
    }

    if (!m_excelApp) {
        if (!initExcel()) {
            return false;
        }
    }

    // 关闭已有工作簿
    closeWorkbook();

    QAxObject* workbooks = m_excelApp->querySubObject("Workbooks");
    if (!workbooks) {
        setLastError("获取工作簿集合失败");
        return false;
    }

    // 打开工作簿(参数:文件名,只读?,更新链接?)
    m_workbook = workbooks->dynamicCall("Open(const QString&)", filePath).value<QAxObject*>();
    delete workbooks;

    if (!m_workbook || m_workbook->isNull()) {
        setLastError(QString("打开工作簿失败:%1").arg(filePath));
        return false;
    }

    // 默认选中第一个工作表
    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    if (worksheets) {
        m_worksheet = worksheets->querySubObject("Item(int)", 1);
        delete worksheets;
    }

    m_currentFilePath = filePath;
    setLastError("");
    return true;
}

// 保存工作簿
bool ExcelHelper::saveWorkbook(const QString& path)
{
    if (!m_workbook) {
        setLastError("未打开/创建工作簿");
        return false;
    }

    QString savePath = path.isEmpty() ? m_currentFilePath : path;
    if (savePath.isEmpty()) {
        setLastError("保存路径为空,请使用saveWorkbookAs指定路径");
        return false;
    }

    // 保存
    m_workbook->dynamicCall("SaveAs(const QString&)", savePath);
    if (m_workbook->isNull()) {
        setLastError(QString("保存工作簿失败:%1").arg(savePath));
        return false;
    }

    m_currentFilePath = savePath;
    setLastError("");
    return true;
}

// 另存为
bool ExcelHelper::saveWorkbookAs(const QString& filePath)
{
    return saveWorkbook(filePath);
}

// 关闭当前工作簿
void ExcelHelper::closeWorkbook()
{
    if (m_worksheet) {
        delete m_worksheet;
        m_worksheet = nullptr;
    }

    if (m_workbook) {
        m_workbook->dynamicCall("Close(bool)", false); // false=不保存更改
        delete m_workbook;
        m_workbook = nullptr;
    }

    m_currentFilePath.clear();
}

// 创建新工作表
bool ExcelHelper::createWorksheet(const QString& sheetName)
{
    if (!m_workbook) {
        setLastError("未打开/创建工作簿");
        return false;
    }

    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    if (!worksheets) {
        setLastError("获取工作表集合失败");
        return false;
    }

    // 检查工作表是否已存在
    QStringList sheets = allWorksheetNames();
    if (sheets.contains(sheetName)) {
        setLastError(QString("工作表已存在:%1").arg(sheetName));
        delete worksheets;
        return false;
    }

    // 添加新工作表
    QAxObject* newSheet = worksheets->dynamicCall("Add()").value<QAxObject*>();
    if (newSheet) {
        newSheet->setProperty("Name", sheetName);
        m_worksheet = newSheet; // 切换到新工作表
    } else {
        setLastError(QString("创建工作表失败:%1").arg(sheetName));
    }

    delete worksheets;
    return newSheet != nullptr;
}

// 删除工作表
bool ExcelHelper::deleteWorksheet(const QString& sheetName)
{
    if (!m_workbook) {
        setLastError("未打开/创建工作簿");
        return false;
    }

    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    if (!worksheets) {
        setLastError("获取工作表集合失败");
        return false;
    }

    QAxObject* sheet = worksheets->querySubObject("Item(const QString&)", sheetName);
    if (!sheet) {
        setLastError(QString("工作表不存在:%1").arg(sheetName));
        delete worksheets;
        return false;
    }

    // 删除工作表
    sheet->dynamicCall("Delete()");
    if (m_worksheet == sheet) {
        m_worksheet = nullptr; // 若删除当前工作表,置空
    }

    delete sheet;
    delete worksheets;
    setLastError("");
    return true;
}

// 重命名工作表
bool ExcelHelper::renameWorksheet(const QString& oldName, const QString& newName)
{
    if (!m_workbook) {
        setLastError("未打开/创建工作簿");
        return false;
    }

    // 检查新名称是否已存在
    QStringList sheets = allWorksheetNames();
    if (sheets.contains(newName)) {
        setLastError(QString("新工作表名称已存在:%1").arg(newName));
        return false;
    }

    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    QAxObject* sheet = worksheets->querySubObject("Item(const QString&)", oldName);
    if (!sheet) {
        setLastError(QString("工作表不存在:%1").arg(oldName));
        delete worksheets;
        return false;
    }

    sheet->setProperty("Name", newName);
    delete sheet;
    delete worksheets;

    // 若重命名的是当前工作表,更新名称关联
    if (m_worksheet && currentWorksheetName() == oldName) {
        m_worksheet->setProperty("Name", newName);
    }

    setLastError("");
    return true;
}

// 切换工作表
bool ExcelHelper::selectWorksheet(const QString& sheetName)
{
    if (!m_workbook) {
        setLastError("未打开/创建工作簿");
        return false;
    }

    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    QAxObject* sheet = worksheets->querySubObject("Item(const QString&)", sheetName);
    delete worksheets;

    if (!sheet || sheet->isNull()) {
        setLastError(QString("切换工作表失败:%1不存在").arg(sheetName));
        return false;
    }

    if (m_worksheet) {
        delete m_worksheet;
    }
    m_worksheet = sheet;
    setLastError("");
    return true;
}

// 获取当前工作表名称
QString ExcelHelper::currentWorksheetName() const
{
    if (!m_worksheet) {
        return "";
    }
    return m_worksheet->property("Name").toString();
}

// 获取所有工作表名称
QStringList ExcelHelper::allWorksheetNames() const
{
    QStringList names;
    if (!m_workbook) {
        return names;
    }

    QAxObject* worksheets = m_workbook->querySubObject("Worksheets");
    if (!worksheets) {
        return names;
    }

    int count = worksheets->property("Count").toInt();
    for (int i = 1; i <= count; ++i) {
        QAxObject* sheet = worksheets->querySubObject("Item(int)", i);
        if (sheet) {
            names.append(sheet->property("Name").toString());
            delete sheet;
        }
    }

    delete worksheets;
    return names;
}

// 读取单元格数据
QVariant ExcelHelper::readCell(int row, int col)
{
    if (!m_worksheet || row < 1 || col < 1) {
        setLastError(QString("读取单元格失败:无效参数(行:%1,列:%2)").arg(row).arg(col));
        return QVariant();
    }

    QAxObject* range = getRange(row, col);
    if (!range) {
        return QVariant();
    }

    QVariant value = range->property("Value");
    delete range;

    // 处理日期类型(Excel日期是浮点数,需转换)
    if (value.type() == QVariant::Double) {
        QAxObject* cell = getRange(row, col);
        QString cellFormat = cell->querySubObject("NumberFormat")->property("Value").toString();
        delete cell;

        if (cellFormat.contains("date", Qt::CaseInsensitive)) {
            // Excel起始日期:1900-01-01(注意Excel的闰年bug,这里简化处理)
            QDateTime baseDate = QDateTime(QDate(1900, 1, 1));
            qint64 days = value.toDouble() - 2; // 修正bug
            value = baseDate.addDays(days).date();
        }
    }

    setLastError("");
    return value;
}

// 写入单元格数据
bool ExcelHelper::writeCell(int row, int col, const QVariant& value)
{
    if (!m_worksheet || row < 1 || col < 1) {
        setLastError(QString("写入单元格失败:无效参数(行:%1,列:%2)").arg(row).arg(col));
        return false;
    }

    QAxObject* range = getRange(row, col);
    if (!range) {
        return false;
    }

    range->setProperty("Value", value);
    delete range;

    setLastError("");
    return true;
}

// 批量读取区域
QVector<QVector<QVariant>> ExcelHelper::readRange(int startRow, int startCol, int endRow, int endCol)
{
    QVector<QVector<QVariant>> data;
    if (!m_worksheet || startRow > endRow || startCol > endCol || startRow < 1 || startCol < 1) {
        setLastError("批量读取区域失败:无效参数");
        return data;
    }

    QAxObject* range = getRange(startRow, startCol, endRow, endCol);
    if (!range) {
        return data;
    }

    // 获取区域数据(二维数组)
    QVariant var = range->property("Value");
    delete range;

    if (var.canConvert<QVariantList>()) {
        QVariantList rows = var.toList();
        for (const QVariant& rowVar : rows) {
            if (rowVar.canConvert<QVariantList>()) {
                data.append(rowVar.toList().toVector());
            }
        }
    }

    setLastError("");
    return data;
}

// 批量写入区域
bool ExcelHelper::writeRange(int startRow, int startCol, const QVector<QVector<QVariant>>& data)
{
    if (!m_worksheet || data.isEmpty() || startRow < 1 || startCol < 1) {
        setLastError("批量写入区域失败:无效参数");
        return false;
    }

    int endRow = startRow + data.size() - 1;
    int endCol = startCol + data.first().size() - 1;

    QAxObject* range = getRange(startRow, startCol, endRow, endCol);
    if (!range) {
        return false;
    }

    // 转换为QVariantList(COM要求的格式)
    QVariantList rows;
    for (const auto& row : data) {
        rows.append(row.toList());
    }

    range->setProperty("Value", rows);
    delete range;

    setLastError("");
    return true;
}

// 设置单元格字体
bool ExcelHelper::setCellFont(int row, int col, const QFont& font)
{
    if (!m_worksheet || row < 1 || col < 1) {
        setLastError("设置字体失败:无效参数");
        return false;
    }

    QAxObject* range = getRange(row, col);
    if (!range) {
        return false;
    }

    QAxObject* fontObj = range->querySubObject("Font");
    if (fontObj) {
        fontObj->setProperty("Name", font.family());
        fontObj->setProperty("Size", font.pointSize());
        fontObj->setProperty("Bold", font.bold());
        fontObj->setProperty("Italic", font.italic());
        fontObj->setProperty("Underline", font.underline() ? 2 : 0); // 2=单下划线
        delete fontObj;
    }

    delete range;
    setLastError("");
    return true;
}

// 设置单元格背景色
bool ExcelHelper::setCellBackgroundColor(int row, int col, const QColor& color)
{
    if (!m_worksheet || row < 1 || col < 1) {
        setLastError("设置背景色失败:无效参数");
        return false;
    }

    QAxObject* range = getRange(row, col);
    if (!range) {
        return false;
    }

    QAxObject* interior = range->querySubObject("Interior");
    if (interior) {
        // Excel颜色值:RGB转换为整数
        int rgb = color.red() + (color.green() << 8) + (color.blue() << 16);
        interior->setProperty("Color", rgb);
        delete interior;
    }

    delete range;
    setLastError("");
    return true;
}

// 设置单元格对齐方式
bool ExcelHelper::setCellAlignment(int row, int col, Qt::AlignmentFlag hAlign, Qt::AlignmentFlag vAlign)
{
    if (!m_worksheet || row < 1 || col < 1) {
        setLastError("设置对齐方式失败:无效参数");
        return false;
    }

    QAxObject* range = getRange(row, col);
    if (!range) {
        return false;
    }

    // 转换Qt对齐方式到Excel对齐方式
    int hAlignExcel = -4108; // 默认居中
    if (hAlign == Qt::AlignLeft) hAlignExcel = -4131;
    else if (hAlign == Qt::AlignRight) hAlignExcel = -4152;

    int vAlignExcel = -4108; // 默认居中
    if (vAlign == Qt::AlignTop) vAlignExcel = -4160;
    else if (vAlign == Qt::AlignBottom) vAlignExcel = -4107;

    QAxObject* horizontalAlign = range->querySubObject("HorizontalAlignment");
    if (horizontalAlign) {
        range->setProperty("HorizontalAlignment", hAlignExcel);
        delete horizontalAlign;
    }

    QAxObject* verticalAlign = range->querySubObject("VerticalAlignment");
    if (verticalAlign) {
        range->setProperty("VerticalAlignment", vAlignExcel);
        delete verticalAlign;
    }

    delete range;
    setLastError("");
    return true;
}

// 获取最后错误信息
QString ExcelHelper::lastError() const
{
    return m_lastError;
}

// 获取单元格区域对象(行、列)
QAxObject* ExcelHelper::getRange(int row, int col)
{
    if (!m_worksheet) {
        setLastError("获取单元格区域失败:未选中工作表");
        return nullptr;
    }

    // 转换行列到Excel列名(如1->A,2->B,26->Z,27->AA)
    QString colStr;
    int c = col;
    while (c > 0) {
        int remainder = (c - 1) % 26;
        colStr.prepend(QChar('A' + remainder));
        c = (c - 1) / 26;
    }

    QString rangeStr = QString("%1%2").arg(colStr).arg(row);
    return getRange(rangeStr);
}

// 获取单元格区域对象(区域字符串)
QAxObject* ExcelHelper::getRange(const QString& rangeStr)
{
    if (!m_worksheet) {
        setLastError("获取单元格区域失败:未选中工作表");
        return nullptr;
    }

    QAxObject* range = m_worksheet->querySubObject("Range(const QString&)", rangeStr);
    if (!range || range->isNull()) {
        setLastError(QString("获取单元格区域失败:%1").arg(rangeStr));
        delete range;
        return nullptr;
    }

    return range;
}

// 获取单元格区域对象(起始/结束行列)
QAxObject* ExcelHelper::getRange(int startRow, int startCol, int endRow, int endCol)
{
    QString startRange = getRange(startRow, startCol)->property("Address").toString();
    QString endRange = getRange(endRow, endCol)->property("Address").toString();
    QString rangeStr = QString("%1:%2").arg(startRange).arg(endRange);
    return getRange(rangeStr);
}

// 设置错误信息
void ExcelHelper::setLastError(const QString& error)
{
    m_lastError = error;
    if (!error.isEmpty()) {
        qWarning() << "[ExcelHelper Error]:" << error;
    }
}

四、核心功能详解

4.1 单例模式设计

ExcelHelper采用单例模式,避免重复创建Excel应用实例(多次创建会导致Excel进程残留)。通过instance()获取唯一实例,确保全局Excel操作的一致性。

4.2 Excel应用初始化与释放

  • initExcel():创建Excel.Application COM对象,设置Visible=false(后台操作)、DisplayAlerts=false(禁用警告弹窗);
  • releaseExcel():关闭工作簿、调用Quit()退出Excel应用、释放COM对象,避免Excel进程残留。

4.3 工作簿与工作表管理

  • 工作簿操作:支持创建新工作簿、打开已有文件、保存/另存为、关闭工作簿;
  • 工作表操作:创建、删除、重命名、切换工作表,获取工作表列表;
  • 关键细节:操作工作表前需检查工作簿是否存在,避免空指针异常。

4.4 单元格数据读写

4.4.1 单个单元格
  • readCell():读取单元格值,自动处理日期类型(Excel日期以浮点数存储,需转换为QDate);
  • writeCell():支持写入任意QVariant类型(字符串、数字、日期等)。
4.4.2 批量读写(性能优化)
  • 批量操作通过一次性读写单元格区域,大幅减少COM交互次数,相比逐单元格操作,效率提升10倍以上;
  • readRange()/writeRange():接收/返回二维QVector,适配表格数据结构。

4.5 单元格格式设置

  • 字体设置:支持字体名称、大小、加粗、斜体、下划线;
  • 背景色:将QColor转换为Excel RGB值;
  • 对齐方式:映射Qt对齐枚举到Excel对齐常量(如Qt::AlignLeft对应Excel的-4131)。

五、实战案例演示

5.1 案例1:创建Excel文件并写入数据

cpp 复制代码
#include "ExcelHelper.h"
#include <QApplication>
#include <QFont>
#include <QColor>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    ExcelHelper& excel = ExcelHelper::instance();

    // 1. 创建新工作簿
    if (!excel.createWorkbook()) {
        qCritical() << "创建工作簿失败:" << excel.lastError();
        return -1;
    }

    // 2. 创建并切换到新工作表
    if (!excel.createWorksheet("销售数据")) {
        qCritical() << "创建工作表失败:" << excel.lastError();
        return -1;
    }

    // 3. 写入表头(设置格式)
    excel.writeCell(1, 1, "日期");
    excel.writeCell(1, 2, "销售额");
    excel.writeCell(1, 3, "区域");

    // 设置表头格式:加粗、背景色、居中对齐
    QFont headerFont;
    headerFont.setBold(true);
    headerFont.setPointSize(12);

    excel.setCellFont(1, 1, headerFont);
    excel.setCellFont(1, 2, headerFont);
    excel.setCellFont(1, 3, headerFont);

    excel.setCellBackgroundColor(1, 1, QColor(200, 200, 200));
    excel.setCellBackgroundColor(1, 2, QColor(200, 200, 200));
    excel.setCellBackgroundColor(1, 3, QColor(200, 200, 200));

    excel.setCellAlignment(1, 1, Qt::AlignCenter, Qt::AlignCenter);
    excel.setCellAlignment(1, 2, Qt::AlignCenter, Qt::AlignCenter);
    excel.setCellAlignment(1, 3, Qt::AlignCenter, Qt::AlignCenter);

    // 4. 批量写入数据
    QVector<QVector<QVariant>> data;
    data.append({"2025-01-01", 12000, "华东"});
    data.append({"2025-01-02", 15000, "华北"});
    data.append({"2025-01-03", 18000, "华南"});

    if (!excel.writeRange(2, 1, data)) {
        qCritical() << "批量写入数据失败:" << excel.lastError();
        return -1;
    }

    // 5. 保存文件
    if (!excel.saveWorkbookAs("D:/销售数据.xlsx")) {
        qCritical() << "保存文件失败:" << excel.lastError();
        return -1;
    }

    // 6. 释放资源
    excel.releaseExcel();

    qInfo() << "Excel文件创建成功!";
    return 0;
}

5.2 案例2:读取Excel文件数据

cpp 复制代码
#include "ExcelHelper.h"
#include <QApplication>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    ExcelHelper& excel = ExcelHelper::instance();

    // 1. 打开Excel文件
    if (!excel.openWorkbook("D:/销售数据.xlsx")) {
        qCritical() << "打开文件失败:" << excel.lastError();
        return -1;
    }

    // 2. 切换到工作表
    if (!excel.selectWorksheet("销售数据")) {
        qCritical() << "切换工作表失败:" << excel.lastError();
        return -1;
    }

    // 3. 读取表头
    QString col1 = excel.readCell(1, 1).toString();
    QString col2 = excel.readCell(1, 2).toString();
    QString col3 = excel.readCell(1, 3).toString();
    qInfo() << "表头:" << col1 << col2 << col3;

    // 4. 批量读取数据
    auto data = excel.readRange(2, 1, 4, 3);
    for (int i = 0; i < data.size(); ++i) {
        qInfo() << QString("第%1行:").arg(i+2)
                << data[i][0].toString()
                << data[i][1].toInt()
                << data[i][2].toString();
    }

    // 5. 释放资源
    excel.releaseExcel();

    return 0;
}

六、常见问题与解决方案

6.1 Excel进程残留

  • 原因 :COM对象未正确释放、Excel应用未调用Quit()
  • 解决方案
    1. 确保releaseExcel()被调用(如程序退出前、异常时);
    2. 禁用Excel的自动保存和提示;
    3. 调试时通过任务管理器结束残留的EXCEL.EXE进程。

6.2 中文乱码

  • 原因:COM组件编码不匹配;
  • 解决方案 :确保Qt工程编码为UTF-8,写入数据时使用QString原生格式,避免手动转换编码。

6.3 大文件读写性能低

  • 原因:逐单元格操作导致频繁COM交互;
  • 解决方案 :使用readRange()/writeRange()批量操作,减少COM调用次数。

6.4 工作表名称重复/不存在

  • 解决方案 :操作工作表前先调用allWorksheetNames()检查名称是否存在。

6.5 64位/32位兼容性

  • 问题:64位Qt程序需调用64位Excel COM组件,32位同理;
  • 解决方案:确保Qt编译架构与Excel安装架构一致(均为32位或64位)。

七、扩展与优化建议

7.1 功能扩展

  • 支持单元格合并/拆分;
  • 支持公式写入(如=SUM(A2:A4));
  • 支持行高/列宽设置;
  • 支持图表插入。

7.2 性能优化

  • 批量操作时禁用Excel的屏幕更新(m_excelApp->setProperty("ScreenUpdating", false));
  • 操作完成后恢复屏幕更新,减少资源占用。

7.3 跨平台兼容

  • QAxObject仅支持Windows,若需跨平台,可结合LibXL、QtXlsxWriter等库;
  • 封装抽象接口,根据平台选择不同实现。

八、总结

本文基于Qt QAxObject封装了一套完整的Excel操作工具类,涵盖工作簿、工作表、单元格的核心操作,并结合实战案例演示了文件创建、数据读写、格式设置的完整流程。该工具类具有高复用性、易扩展的特点,可直接集成到Qt项目中解决Excel操作需求。

使用时需注意Windows平台限制、COM对象的正确释放、批量操作的性能优化等问题,确保程序稳定运行。对于跨平台需求,可在该工具类基础上扩展其他Excel操作库的适配层,实现一套接口多平台兼容。

相关推荐
爱装代码的小瓶子4 小时前
【c++知识铺子】map和set的底层-红黑树
java·开发语言·c++
雪域迷影4 小时前
macOS中使用cJSON解析库解析JSON
c++·macos·json·c·cmake·pkg-config
江湖人称贺行风4 小时前
C++八股
c++·八股
枫叶丹44 小时前
【Qt开发】Qt窗口(八) -> QFileDialog 文件对话框
c语言·开发语言·数据库·c++·qt
qq_479875434 小时前
深入解析 Protobuf 消息的分帧 (Framing) 与编码 (Codec)
linux·c++
chenyuhao20244 小时前
Linux系统编程:进程控制
linux·运维·服务器·开发语言·c++·后端
落羽的落羽4 小时前
【Linux系统】进程终止、进程等待与进程替换的概念与实现
linux·服务器·c++·人工智能·深度学习·机器学习·游戏引擎
小年糕是糕手4 小时前
【C++】模板初阶
java·开发语言·javascript·数据结构·c++·算法·leetcode
脏脏a4 小时前
C++ 字符串处理利器:STL string 保姆级入门教程
开发语言·c++