我是靠谱客的博主 魁梧机器猫,最近开发中收集的这篇文章主要介绍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,从而提高写入的效率

最后

以上就是魁梧机器猫为你收集整理的QAxObject写Excel(详解)的全部内容,希望文章能够帮你解决QAxObject写Excel(详解)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部