QAxObject写Excel(详解)

【前言】
QAxObject继承自QObject和QAxBase,QAxBase提供了通过IUnknown指针直接访问COM对象的API,因此QAxObject可以对Excel(一种COM对象)进行读写操作。此外QAxObject还继承了QAxBase的大部分与ActiveX相关的功能,特别是dynamicCall()和querySubObject(),对于Excel的操作主要使用这两种方法。
【Excel】
主要层级:
    Excel->Workbooks(工作簿集合)->Workbook(工作簿)->Worksheets(工作表集合)->Worksheet(工作表)->cell(单元格)

 

【QAxObject的主要方法】
1. QAxObject
  • 函数声明:
    • QAxObject(IUnknown *iface, QObject *parent = nullptr)
    • QAxObject(const QString &c, QObject *parent = nullptr)
    • QAxObject(QObject *parent = nullptr)
  • 作用:三种构造函数,第一种根据传入的IUnknown指针iface加载其指向的COM对象;第二种加载指定的COM对象c;第三种没有设置加载的COM对象

2. querySubObject
  • 函数声明:QAxObject* QAxBase::querySubObject(const char *name, QList<QVariant> &vars)
  • 作用:获取子对象。若name为对象的属性名,则忽略vars;若name为函数原型,则vars为入参列表,当函数有出参则更新到var
// 设置Excel控件
QAxObject* excel = new QAxObject("Excel.Application");
// 获取工作簿集合
QAxObject* workBooks = excel->querySubObject("WorkSheets");
// 获取当前工作簿
QAxObject* workBook = workBooks->querySubObject("ActiveWorkBook");
// 获取第n个工作簿
workBook = workBooks->querySubObject("Item(int)", n);
// 获取工作表集合
QAxObject* workSheets = workBook->querySubObject("WorkSheets");
// 获取当前工作表
QAxObject* workSheet = workSheets->querySubObject("ActiveSheet");
// 获取第n个工作表
workSheet = workSheets->querySubObject("Item(int)", n);

// 获取第i行,第j列的单元格
QAxObject* cell = worksheet->querySubObject("Cells(int, int)", i, j);
// 获取第i列的所有单元格
QAxObject* range = worksheet->querySubObject("Range(QString)", "i");
// 获取第i列到第j列的所有单元格
QAxObject* range = worksheet->querySubObject("Range(QString)", "i:j");
QAxObject* range = worksheet->querySubObject("Range(QString, QString)", "i", "j");

3. dynamicCall
  • 函数声明:QVariant QAxBase::dynamicCall(const char *function, QList<QVariant> &vars)
  • 作用:调用COM对象的方法函数并返回对应的返回值。function为函数原型,vars为入参列表。若函数无返回值或者调用失败则返回无效的QVariant对象,当函数有出参则更新到vars
// 增加新的工作簿
excel->dynamicCall("Add()");
// 退出Excel程序
excel->dynamicCall("Quit()");
// 关闭工作簿
workBook->dynamicCall("Close(bool)", true);
// 保存工作簿
workBook->dynamicCall("Save()");
// 工作簿保存到fileName
workBook->dynamicCall("SaveAs(const QString&)", fileName);

4. setControl

  • 函数声明:bool setControl(const QString& name)

  • 作用:设置操作的COM对象。一个对象只能操作一个COM对象,后设置的将覆盖先前的设置。主要设置方式有四种,效率以先后次序递

    • 注册组件的UUID(CLSID):axObj->setControl("{8E27C92B-1264-101C-8A2F-040224009C02}");

    • 注册组件类名PROGID(版本号可含可不含):axObj->setControl("MSCal.Calendar");

    • 组件的全称:axObj->setControl("Calendar Control 9.0");

    • 组件的路径名:axObj->setControl("c:/files/file.doc");

QAxObject* axObj = new QAxObject();
axObj->setControl("Excel.Application")     // 设置Excel控件
axObj->setControl("ET.Application")        // 设置WPS-Excel控件        
axObj->setControl("Word.Application")      // 设置Word控件

Office与WPS的注册ID映射表

COM组件PROGIDCLSID
Office WordWord.Application{00020906-0000-0000-C000-000000000046}
Office ExcelExcel.Application{00020812-0000-0000-C000-000000000046}
Office PPTPowerpoint.Application{91493441-5a91-11cf-8700-00aa0060263b}
WPS WordKWPS.Application{000209FF-0000-4b30-A977-D214852036FF}
WPS Excel(旧版)ET.Application{45540001-5750-5300-4B49-4E47534F4654}
WPS Excel(新版)KET.Application{45540001-5750-5300-4B49-4E47534F4655}
WPS PPTKWPP.Application{44720441-94BF-4940-926D-4F38FECF2A48}

5. setProperty
  • 函数声明:bool setProperty(const char *name, const QVariant& var)
  • 作用:设置对象的name属性的值为var。对于存在相同作用的dynamicCall,setProperty和property(获取属性值)性能更优
// 不自动打开excel
excel->setProperty("Visible", false);
// 警告不进行弹窗提示
excel->setProperty("DisplayAlerts", false);
// 设置单元的值
QVariant value;
range->setProperty("Value", value);
// 设置单元的格式
range->setProperty("NumberFormatLocal", "@");
// 设置单元的水平对齐方式
range->setProperty("HorizontalAlignment", hAlignment);
// 设置单元的垂直对齐方式
range->setProperty("VerticalAlignment", vAlignment);


6. isNull
  • 函数声明:bool QAxBase::isNull() const
  • 作用:若对象没有加载COM对象则返回true,否则返回false

7. asVariant
  • 函数声明:QVariant asVariant() const
  • 作用:返回加载COM对象的QVariant。一般用于dynamicCall的入参列表
int sheetCount = workSheets->property("Count").toInt();                        // 获取工作表数量
QAxObject* lastSheet = workSheets->querySubObject("Item(int)", sheetCount);    // 获取最后一个工作表
workSheets->dynamicCall("Add(QVariant)",lastSheet->asVariant());               // 在最后一个表前新增一个工作表
QAxObject* newSheet = workSheets->querySubObject("Item(int)", sheetCount);     // 获取新表
lastSheet->dynamicCall("Move(QVariant)", newSheet->asVariant());               // 将最后的表移动到新表之前
注:上述方法及其余方法可以查看QT官方文档 
QAxBaseicon-default.png?t=M85Bhttps://doc.qt.io/qt-5/qaxbase.html QAxObject icon-default.png?t=M85Bhttps://doc.qt.io/qt-5/qaxobject.html
【自定义写Excel函数】
 
  1. Excel的创建和相关初始化
void init()
{
    excel = new QAxObject();                // 初始化
    if (nullptr == excel)
    {
        return;
    }
    excel->setControl("Excel.Application"); // 加载Excel控件
    if (excel->isNull())
    {
        excel->setControl("KET.Application"); // 若上面加载失败,则尝试加载WPS-Excel
    }
    excel->setProperty("Visible", false);        // 不显示Excel
    excel->setProperty("DisplayAlerts", false);  // 不弹出警告提示窗

    workbooks = excel->querySubObject("WorkSheets");    // 获取工作簿集合
    if (nullptr == workbooks)
    {
        excel->dynamicCall("Quit()");
        return;
    }
    workbooks->dynamicCall("Add()");    // 新增一个工作簿
    workbook = workbooks->querySubObject("ActiveWorkBook");    // 获取当前活动工作簿
    worksheets = workbook->querySubObject("WorkSheets");       // 获取工作表集合
}
  2. 新增工作表
void appendSheet()
{
    int sheetCount = worksheets->property("Count").toInt();                        // 获取工作表的数量
    QAxObject* lastSheet = worksheets->querySubObject("Item(int)", sheetCount);    // 获取最后一个工作表lastSheet
    worksheets->dynamicCall("Add(QVariant)", lastSheet->asVariant());              // 在lastSheet之前插入一个新工作表
    QAXObject* newSheet = worksheets->querySubObject("Item(int)", sheetCount);     // 获取新增的工作表newSheet
    lastSheet->dynamicCall("Move(QVariant)", newSheet->asVariant());               // 将lastSheet移动到newSheet之前
}

// 注意:
// Add(QVariant var):新增一个对象插入到var对应的对象“之前”
// Move(QVariant var):将调用对象移动到var对应对象“之前”
// 若worksheets直接调用Add(),入参为空,则是在活动工作表之前插入一个新的工作表
  3. 数据写入
bool write(const QString& filename, const QList<QList<QVariant>>& datas)
{
    // 根据excel的格式类型获取单个工作表的最大行数限制,一般xls为65536,xlsx为1048576(不同版本的excel软件可能不一样)
    QFileInfo fileInfo(filename);
    int limitMaxRowCount= "xls" == fileInfo.completeSuffix() ? 65536 : 1048576;

    QList<QList<QVariant>> curDatas;
    int rowCount = 0;
    int curSheetIndex= 0;
    for (auto& rowData : datas)
    {
        ++rowCount;
        curDatas.append(rowData);
        // 写入数据超过单个工作表最大的行数限制,则需将多余的数据写入新的工作表中
        if (0 == rowCount % limitMaxRowCount)
        {
            ++curSheetIndex;
            if (!writeDatasToSheet(curSheetIndex))
            {
                return false;
            }
        }
    }
    if (!writeDatasToSheet(curSheetIndex + 1))
    {
        return false;
    }

    // 保存
    saveAs(filename);

    return true;
}

// 获取加载的COM对象工作表的最大行数限制(该方法不适用于xlsx和xls的区别)
int getMaxLimitRowCount() const
{
    int count = INT_MAX;
    if(nullptr != worksheet && !worksheet->isNull())
    {
        QAxObject* colObj = worksheet->querySubObject("Columns");
        if(nullptr != colObj)
        {
            count = colObj->property("Count").toInt();
        }
    }
    return count;
}

// 数据写入
bool writeDatasToSheet(int sheetIndex, const QList<QList<QVariant>>& datas)
{
    if (setCurrentSheet(currentSheetIndex))
    {
        return false;
    }
    if (writeCurrentSheet(curDatas))
    {
        return false;
    }
    return true;
}

// 插入新的工作表并设置活动工作表
bool setCurrentSheet(int index)
{
    bool ret = false;
    if (nullptr != worksheets && !worksheets->isNull())
    {
        delete worksheet;
        worksheet = nullptr;
        
        // 尝试插入新的工作表
        int sheetCount = worksheets->property("Count").toInt();
        if (sheetCount < index)
        {
            appendSheet();
        }
        // 获取并激活工作表
        worksheet = worksheets->querySubObject("Item(int)", index);
        ret = nullptr != worksheet && !worksheet ->isNull();
        if (ret)
        {
            worksheet ->dynamicCall("Activate(void)");
        }
    }
    return ret;
}

// 向活动工作表写入数据
bool writeCurrentSheet(const QList<QList<QVariant>>& datas)
{
    if (nullptr == worksheet || datas.empty())
    {
        return false;
    }
    int rows = datas.size();
    int cols = datas[0].size();
    // 获取需写入的单元格范围
    QString rangStr = "";
    convertColIndexToName(cols, rangStr);    
    rangeStr += QString::number(rows);
    rangeStr = "A1:" + rangeStr;
    QAxObject* range = worksheet->querySubObject("Range(const QString&)", rangeStr);
    if (nullptr == range)
    {
        return false;
    }
    // 设置单元格的数据格式
    range->setProperty("NumberFormatLocal", "@");
    // 将数据从QList<QList<QVariant>>转换为QVariant
    QVariant varCurData;
    convertListListVarToVar(datas, varCurData);
    return range->setProperty("Value2", varCurData);
}

// 计算最后一列的标识
void convertColIndexToName(int cols, QString& colName)
{
    int tempData = cols / 27;
    if (tempData > 0)
    {
        int mode = cols % 26;
        convertColIndexToName(mode, colName);
        convertColIndexToName(tempData, colName);
    }
    else
    {
        colName = (to26AlphabetString(cols) + colName);
    }
}

// 根据数字索引获取对应的字母,1-A、2-B以此类推
QString numberToAlphabetStr(int n)
{
    QChar ch = 0x40 + n;
    return QString(ch);
}

// QList<QList<QVariant>>转换成QVariant
void convertListListVarToVar(const QList<QList<QVariant>>& datas, QVariant& var)
{
    QVariantList varList;
    for (int i = 0; i < datas.size(); ++i)
    {
       varList.append(QVariant(datas[i]));
    }
        var = QVariant(varList);
}
  4. 数据保存
// 保存
void save()
{
    if (nullptr != excel)
    {
        excel->dynamicCall("Save()");
    }
}

// 另存为
void saveAs(cons QString& filename)
{
    if (nullptr != workbook && !workbook->isNull())
    {
        QFileInfo fileInfo(filename);
        int nFileType = ("xlsx" == fileInfo.completeSuffix()) ? 51 : 56;

        // 文件路径需要将'/'改为'\\'
        QString path = filename;
        path = path.replace("/", "\\");
        // 第二种方式:path = QDir::toNativeSeparators(filename);
        
        // 调用保存方法
        workbook->dynamicCall("SaveAs(const QString&,int,const QString&,const QString&,bool,bool)", 
            strPath, nFileType, QString(""), QString(""), false, false);
    }
}
  5. Excel程序退出
void exit()
{
    if (nullptr != excel)
    {
        excel->dynamicCall("Quit()");

        delete excel;
        excel = nullptr;
    }
}
【实战注意事项】
1.加载COM组件对象失败
(1)应用程序没有初始化COM库

// 需包含头文件Ole2.h

// 在操作COM对象的API前初始化COM库

HRESULT r = OleInitialize(0);

if (S_OK != r && S_FALSE != r)

{

    qWarning("Qt:初始化Ole失败(error %x)", static_cast<unsigned int>(r));

}

// 使用完毕后需要释放COM库

OleUninitialize();

// 注意:OleInitialize和OleUninitialize的调用次数要一致

(2)注册表中的数据有问题
Win+R打开cmd,输入dcomcnfg打开组件服务,“控制台节点”=>“组件服务”=>“计算机”=>“我的电脑”=>“DCOM配置”,查看Microsoft Excel Application的属性,记录下应用程序ID和本地路径。命令输入regedit打开注册表编辑器,Ctrl+F查找刚才记录下的ID,其下的LocalServer和LocalServer32中的数据需要与在之前记录的本地路径一致
(3)组件服务的权限问题
组件服务中打开Microsoft Excel Application的属性,修改其中的“启动和激活权限”、“访问权限”以及“配置权限”
(4)注册表中的WPS需要注册
// 注册WPS的KET
void registerKet()
{
    QProcess p(NULL);
    p.setStandardInputFile("\\log.txt");

    vector<QString> vCommand;
    vCommand.reserve(3);
    vCommand.push_back("reg copy HKCU\\SOFTWARE\\Classes\\KET.Application HKLM\\SOFTWARE\\Classes\\KET.Application /s /f");
    vCommand.push_back("reg copy HKCU\\SOFTWARE\\Classes\\KET.Application.9 HKLM\\SOFTWARE\\Classes\\KET.Application.9 /s /f");
    vCommand.push_back("reg copy HKCU\\SOFTWARE\\Classes\\CLSID\\{45540001-5750-5300-4B49-4E47534F4655} HKLM\\SOFTWARE\\Classes\\CLSID\\{45540001-5750-5300-4B49-4E47534F4655} /s /f");

    for(auto& cmd : vCommand)
    {
        p.start(cmd);
        p.waitForFinished();
    }
}

registerKet();
QAxObject* excel = new QAxObject("KET.Application");
if(nullptr != excel && excel->isNull())
{
    excel->setControl("{45540001-5750-5300-4B49-4E47534F4655}");
}

// 上述方法也适用于Office,只需修改注册的cmd以及对应的CLSID/PROGID
// 代码来源 https://blog.csdn.net/SomeOne75/article/details/123007669

(5)加载控件时用的是PROGID,而实际注册表中没有设置该ID(比较常见)

可以考虑若根据PROGID加载失败时,尝试根据CLSID加载

QAxObject* excel = new QAxObject();
excel->setControl("Excel.Application");
if(excel->isNull())
{
    excel->setControl("{00020812-0000-0000-C000-000000000046}");
}

2. 既能操作Office也能操作WPS
  • 加载控件对象Excel.Application失败,可以尝试加载WPS的KET.Application
  • 设置值和获取值用的是setProperty("Value", value) / property("Value"),"Value"只支持Office,将其中的 "Value"改为"Value2",Office和WPS都支持

3. 进行了写操作但Excel为空,或是Excel不为空但读取不到
  • 文件路径没有进行处理,其中'\'需要转换为"\\"才能正确的获取到相应路径的文件;
  • 设置值或获取值时调用的方法不适用于当前的COM对象(详见2的第二条);

4. 写Excel须注意加载的COM组件的边界限制
  • excel主要有两种格式,xls和xlsx,一般xls单个工作表限制最大行数为65536,xlsx为1048576,因此插入新表再写入额外数据。 可以打开excel文件,选中第一行,再按“Ctrl+↓”可以查看最大行数和最大列数

5. 数据格式问题
  • property("NumberFormatLocal")获取单元格的格式,默认为“G/通用格式”,通过setProperty("NumberFormatLocal", "@")设置单元格为字符串格式。需注意不同版本的excel支持的格式类型可能有差异性

6. 写入的性能优化问题
  • 一般在实际开发中,数据的导出(写Excel)用的比较多。若数据一行一行的写,则需要多次调用querySubObject获取指定范围的单元格1*cols;而直接获取整个工作表中需要写入的单元格rows*cols,一个工作表只需调用一次querySubObject,从而提高写入的效率
  • 9
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: QAxObject 是一个能够在 Qt 程序中操作 Microsoft Excel 的类,它提供了许多功能和方法来读取、入和编辑 Excel 文件。 首先,我们需要在程序中包含 QAxObject 的头文件: ```cpp #include <QAxObject> ``` 然后,我们可以创建一个 QAxObject 对象来操作 Excel: ```cpp QAxObject excel("Excel.Application"); ``` 接下来,我们可以使用 QAxObject 对象来打开或创建一个 Excel 文件: ```cpp QAxObject *workbooks = excel.querySubObject("Workbooks"); QAxObject *workbook = workbooks->querySubObject("Add"); ``` 然后,我们可以获取到当前活动的工作表,并在工作表中入数据: ```cpp QAxObject *worksheets = workbook->querySubObject("Worksheets"); QAxObject *worksheet = worksheets->querySubObject("Item(int)", 1); // 获取第一个工作表 QAxObject *cell = worksheet->querySubObject("Cells(int, int)", 1, 1); // 获取第一个单元格 cell->dynamicCall("SetValue(const QVariant&)", QVariant("Hello, QAxObject!")); // 入数据 ``` 最后,我们可以保存并关闭 Excel 文件: ```cpp workbook->dynamicCall("SaveAs(const QString&)", "path/to/save/excel/file.xlsx"); // 保存文件 excel.dynamicCall("Quit()"); // 关闭 Excel ``` 上述示例展示了如何使用 QAxObject 类在 Qt 程序中通过入数据到 Excel 文件。当然,还有很多其他的功能和方法可以使用,如读取数据、设置样式等等。通过查阅 QAxObject 的文档,我们可以进一步了解如何灵活地操作 Excel。 ### 回答2: QAxObject 是一个用于与 COM 组件进行交互的类,它可以通过编程实现对 Excel 进行读操作。下面是一个使用 QAxObject Excel 的示例代码: ```cpp #include <QAxObject> int main() { // 创建 Excel 应用程序对象 QAxObject* excel = new QAxObject("Excel.Application", 0); // 设置是否可见 excel->setProperty("Visible", false); // 添加一个工作簿 QAxObject* workbooks = excel->querySubObject("Workbooks"); QAxObject* workbook = workbooks->querySubObject("Add()"); // 获取第一个工作表 QAxObject* worksheets = workbook->querySubObject("Worksheets"); QAxObject* worksheet = worksheets->querySubObject("Item(int)", 1); // 在 A1 单元格入数据 QAxObject* range = worksheet->querySubObject("Range(const QString&)", "A1"); range->setProperty("Value", "Hello, World!"); // 保存工作簿 workbook->dynamicCall("SaveAs(const QString&)", "C:/example.xlsx"); // 关闭工作簿和 Excel 应用程序 workbook->dynamicCall("Close()"); excel->dynamicCall("Quit()"); delete range; delete worksheet; delete worksheets; delete workbook; delete workbooks; delete excel; return 0; } ``` 以上代码创建了一个 Excel 应用程序对象,添加了一个工作簿并获取了第一个工作表。然后,通过获取 A1 单元格的范围对象,将数据入该单元格。最后,保存并关闭工作簿以及退出 Excel 应用程序。 通过以上示例,我们可以使用 QAxObject 类轻松地实现用 C++ 编程Excel 文件。 ### 回答3: QAxObject是一个可以在Qt中使用的类,用于与ActiveX对象进行交互。在使用QAxObjectExcel时,我们可以按照以下步骤进行操作: 1. 首先,需要在代码中包含QAxObject的头文件: #include <QAxObject> 2. 创建一个QAxObject对象,并指定其类名为"Excel.Application",代表Excel应用程序: QAxObject *excel = new QAxObject("Excel.Application"); 3. 创建一个工作簿对象,并通过调用excel对象的querySubObject方法获取Workbook对象: QAxObject *workbook = excel->querySubObject("Workbooks"); 4. 调用Workbook对象的add方法创建一个新的工作簿: workbook->dynamicCall("Add"); 5. 获取活动工作表: QAxObject *worksheet = workbook->querySubObject("ActiveSheet"); 6. 设置单元格的值: QAxObject *cell = worksheet->querySubObject("Cells(int, int)", row, column); cell->setProperty("Value", value); 7. 保存工作簿: workbook->dynamicCall("SaveAs(const QString&)", filePath); 8. 关闭工作簿: workbook->dynamicCall("Close()"); 9. 退出Excel应用程序: excel->dynamicCall("Quit()"); 10. 释放QAxObject对象: delete excel; 通过以上步骤,我们可以使用QAxObject在Qt中Excel文件。需要注意的是,运行该代码时需要确保计算机上已安装有Excel应用程序,并且在项目.pro文件中添加了对COM组件的支持,示例:win32:LIBS += -lole32。同时,操作Excel时,需要遵循Excel的COM接口规范。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值