Qt5生成Word格式报告

引言

项目中需要生成word格式的报告文件,初探了Qt5通过word模板生成报告的方法,整理了使用时的环境配置、子线程中使用时的注意事项以及常用的操作方法,于此记录。

环境:vs2012+Qt5.2+word2016

一、使用ActiveQt模块

注意:ActiveQt只适用于windows平台下,linux和macOS版本的Qt中是没有这个库的

首先需要添加库文件,可以直接在VS2012菜单栏Qt5->Qt Project Setting->勾选Active Qt
这里写图片描述
勾选之后再次查看Qt Project Setting,发现自动勾上了Active Qt server,同时“项目属性->配置属性->链接器->输入->附加依赖性”中自动加入了Qt5AxContainerd.lib;Qt5AxBased.lib
此后便可成功include头文件

#include <QAxWidget>
#include <QAxObject>

二、子线程中使用

在使用过程中发现调用word过程比较耗时,会阻塞GUI线程,于是将保存报告操作移到子线程中.
不过在子线程中使用QAxWidget会报错ASSERT failure in QWidget: "Widgets must be created in the GUI thread."这是由于线程里面不能创建GUI对象。
解决方案是用 QAxObjec取代QAxWidget,初始化过程如下:

bool Report::Open(QString Dir)
{
    // 新建一个word应用程序,并设置为不可见
    //m_WordFile = new QAxWidget("Word.Application", 0, Qt::MSWindowsOwnDC);
    m_WordFile = new QAxObject();//取代QAxWidget,使其在子线程中可用
    bool bFlag = m_WordFile->setControl( "word.Application" );
    if(NULL == m_WordFile)
    {
        // 尝试用wps打开
        bFlag = m_WordFile->setControl( "kwps.Application" );
        if(!bFlag)
        {
            return false;
        }
    }
    m_WordFile->setProperty("Visible", false);
    // 获取所有的工作文档
    QAxObject *Documents = m_WordFile->querySubObject("Documents");
    if(NULL == Documents)
    {
        return false;
    }
    // 以文件template.dot为模版新建一个文档
    Documents->dynamicCall("Add(QString)", Dir);

    // 获取当前激活的文档
    m_Document = m_WordFile->querySubObject("ActiveDocument");
    if(NULL == m_Document)
    {
        return false;
    }
    m_bInit = true;
    return true;
}

同时由于在QApplication的主线程中,会自动初始化COM库,而新开辟的子线程不会自动初始化COM库,所以需要我们手动来初始化,方法如下:
添加头文件:

#include <windows.h>  

构造函数中初始化COM库:

Report::Report(QObject *parent)
    : QObject(parent)
{
    HRESULT result = OleInitialize(0);

    if (result != S_OK && result != S_FALSE)
    {
        qDebug()<<QString("Could not initialize OLE (error %x)").arg((unsigned int)result);
    }
    //moveToThread方法产生线程
    this->moveToThread(&m_thread);
    m_thread.start();
}

析构函数中释放:

Report::~Report()
{
    OleUninitialize();
    m_thread.quit();
    m_thread.wait();
}

三、准备word模板

在word文档中手动添加书签(bookmark)后保存为dot格式
这里写图片描述

四、代码

1.插入书签位置

QString outFileName = QFileDialog::getSaveFileName(this, QStringLiteral("请输入要保存的名字:"),".", "Microsoft Word 97-2003(*.doc);;Microsoft Word 2007-2013(*.docx)");
    if (outFileName.isEmpty()) {
        QMessageBox::warning(this, tr("警告"),tr("输入的文件名为空!"),QMessageBox::Ok);
        return ;
    }

    // 新建一个word应用程序,并设置为不可见  
    QAxWidget *word=new QAxWidget("Word.Application", 0, Qt::MSWindowsOwnDC);  
    word->setProperty("Visible", false);  
    // 获取所有的工作文档  
    QAxObject * documents = word->querySubObject("Documents");
    // 以文件testTemplate.dot为模版新建一个文档,注意这里的路径为绝对路径
    QDir dir(".");
    documents->dynamicCall("Add(QString)",QString("%1/testTemplate.dot").arg(dir.absolutePath()));  
    // 获取当前激活的文档  
    QAxObject *document=word->querySubObject("ActiveDocument");  
    // 获取文档中名字为TSName_1_1的标签  
    QString bookmakrName="TSName_1_1";
    QAxObject*bookmark_text=document->querySubObject(QString("Bookmarks(%1)").arg(bookmakrName).toLocal8Bit().data());  
    // 选中标签,将字符插入到标签位置  
    if(!bookmark_text->isNull())  
    {  
        bookmark_text->dynamicCall("Select(void)");  
        bookmark_text->querySubObject("Range")->setProperty("Text",QStringLiteral("测试输入"));  
    }  

    // 将文件另存为outFileName,关闭工作文档,退出应用程序  
    document->dynamicCall("SaveAs (const QString&)", outFileName);  
    document->dynamicCall("Close (boolean)", true);  //关闭文本窗口
    word->dynamicCall("Quit(void)");  //退出word

    delete bookmark_text; 
    delete document;  
    delete documents;  
    delete word;  

2.批量插入

QString outFileName = QFileDialog::getSaveFileName(this, QStringLiteral("请输入要保存的名字:"),".", "Microsoft Word 97-2003(*.doc);;Microsoft Word 2007-2013(*.docx)");
    if (outFileName.isEmpty()) {
        QMessageBox::warning(this, tr("警告"),tr("输入的文件名为空!"),QMessageBox::Ok);
        return ;
    }
    word = new QAxWidget("Word.Application", 0, Qt::MSWindowsOwnDC);
    word->setProperty("Visible", false);
    word->setProperty("DisplayAlerts", true);

    QAxObject *docs = word->querySubObject("Documents");
    if (!docs) {
        QMessageBox::warning(this, tr("警告"), tr("无法获得Documents对象!"),QMessageBox::Ok);
        return ;
    }

    QStringList items;
    QStringList sometexts;
    //items按顺序依次是“待匹配标签名”和“插入的内容”
    items<<"TSName_1_2"<<"TSName222"<<"TSName_1_1"<<"TSName111"<<"555";
    sometexts<<"111"<<"222"<<"333"<<"444"<<"555";

    editBookMarks(docs, sometexts, items, outFileName);

    word->dynamicCall("Quit(boolean)", true);
    delete word;
void testword::editBookMarks(QAxObject *docs, QStringList sometexts, QStringList &itemList, QString outFileName)
{
    QDir dir(".");
    docs->dynamicCall("Add(QString)", QString("%1/testTemplate.dot").arg(dir.absolutePath()));

    QAxObject *currentDoc = word->querySubObject("ActiveDocument");
    if(!currentDoc){
        QMessageBox::warning(this, QStringLiteral("警告"), QStringLiteral("无法获取当前打开文件对象!"),QMessageBox::Ok);
        return;
    }
    QAxObject *allBookmarks = currentDoc->querySubObject("Bookmarks");
    if (!allBookmarks) {
        QMessageBox::warning(this, QStringLiteral("警告"), QStringLiteral("无法获取模板中的书签,请先插入书签!"), QMessageBox::Ok);
        return ;
    }

    int count = allBookmarks->property("Count").toInt();

    /* 填写模板中的书签  */
    for (int i = count; i > 0; --i) {
        QAxObject *bookmark = allBookmarks->querySubObject("Item(QVariant)", i);
        QString name= bookmark->property("Name").toString();
        int j=0;
        foreach(QString itemName , itemList){
            if (name == itemName) {
                QAxObject *curBM = currentDoc->querySubObject("Bookmarks(QString)", name);
                curBM->querySubObject("Range")->setProperty("Text", itemList.at(j+1));
                break;
            }
            j++;
        }
        if (j == itemList.length()) {//如果遍历itemList,未找到匹配的书签,提示输入
            QString text = QInputDialog::getText(this, QStringLiteral("请输入"), QStringLiteral("%1").arg(name));
            bookmark->querySubObject("Range")->setProperty("Text", text);
            itemList.append(name);
            itemList.append(text);
        }
    }
    //依次插入sometexts中内容
    while(!sometexts.isEmpty()){
        QAxObject *currentRange = currentDoc->querySubObject("Range()");
        int rangeEnd = currentRange->property("End").toInt();
        currentRange->dynamicCall("setRange(QVariant, QVariant)", rangeEnd, rangeEnd);
        currentRange->dynamicCall("InsertAfter(QString)", QStringLiteral("\n%1-%3\n").arg(sometexts[0]).arg(1));

        sometexts.removeAt(0);
    }

    currentDoc->dynamicCall("SaveAs(QString&)", outFileName);
    currentDoc->dynamicCall("Close()");
}

效果如下:
这里写图片描述

3.插入表格

方法一:利用Range对象定位后插入表格

/******************************************************************************
 * 函数:intsertTable
 * 功能:创建表格
 * 参数:nStart 开始位置; nEnd 结束位置; row hang; column 列
 * 返回值: void
 *****************************************************************************/
void WordEngine::intsertTable(int nStart, int nEnd, int row, int column)
{   
      QAxObject* ptst = m_wordDocuments->querySubObject( "Range( Long, Long )",
                                                         nStart, nEnd );
       QAxObject* pTable = m_wordDocuments->querySubObject( "Tables" );
       QVariantList params;
       params.append(ptst->asVariant());
       params.append(row);
       params.append(column);
       if( pTable )
       {
         pTable->dynamicCall( "Add(QAxObject*, Long ,Long )",params);
       }
//       QAxObject* table = selection->querySubObject("Tables(1)");
//       table->setProperty("Style", "网格型");
}

方法二:利用bookmark定位后插入表格

QAxObject *WordEngine::insertTable(QString sLabel, int row, int column)  
{  
     QAxObject *bookmark = m_pWorkDocument->querySubObject("Bookmarks(QVariant)", sLabel);  
     if(bookmark)  
     {  
       bookmark->dynamicCall("Select(void)");  
       QAxObject *selection = m_pWord->querySubObject("Selection");  

       selection->dynamicCall("InsertAfter(QString&)", "\n");  
       //selection->dynamicCall("MoveLeft(int)", 1);  
       selection->querySubObject("ParagraphFormat")->dynamicCall("Alignment", "wdAlignParagraphCenter");  
       //selection->dynamicCall("TypeText(QString&)", "Table Test");//设置标题  

       QAxObject *range = selection->querySubObject("Range");  
       QAxObject *tables = m_pWorkDocument->querySubObject("Tables");  
       QAxObject *table = tables->querySubObject("Add(QVariant,int,int)",range->asVariant(),row,column);  

       for(int i=1;i<=6;i++)  
       {  
           QString str = QString("Borders(-%1)").arg(i);  
           QAxObject *borders = table->querySubObject(str.toAscii().constData());  
           borders->dynamicCall("SetLineStyle(int)",1);  
       }  
       return table;  
     }  
} 

插入表格,修改列宽等是后来看到的,可参考这个github项目中的WordEngine实现

五、其他

用以下方法往bookmark插入内容:

QString bookmakrName="TSName1_1_2";//假设dot文件中并没有这个bookmark
    QAxObject* bookmark_text=document->querySubObject("Bookmarks(const QString&)", bookmakrName);  
    if(NULL == bookmark_text)//注意这个判断不可少,否则下面调用isNull()时会出错
    {
        return;
    }
    // 选中标签,将字符插入到标签位置  
    if(!bookmark_text->isNull())  //如果没有匹配到对应的bookmark,直接判断会出错,所以要提前返回
    {  
        bookmark_text->dynamicCall("Select(void)");  
        bookmark_text->querySubObject("Range")->setProperty("Text",QStringLiteral("测试输入"));  
    }

如果dot文件中并没有这个bookmark,会报如下错误:

QAxBase: Error calling IDispatch member Bookmarks: Exception thrown by server
             Code       : 5941
             Source     : Microsoft Word
             Description: ????????????
             Help       : wdmain11.chm [25421]
         Connect to the exception(int,QString,QString,QString) signal to catch this exception

这时判断NULL == bookmark_text返回即可,如果要避免匹配不存在的bookmark,可以在前面处理,比如上文“2.批量插入”中所示的先利用QAxObject *allBookmarks = currentDoc->querySubObject("Bookmarks");获取所有Bookmarks,然后在进行处理。

最后附上相关源码:demo-Qt5生成Word格式报告(demo是最开始写的,未包含插入表格,多线程等方法实现,比较简单)

参考

Qt利用ActiveX生成Word文档
qt中如何使用ActiveX读写word
github-试卷自动生成系统
github-QTScada
QT在子线程中使用QAxWidget需要初始化COM的问题

  • 17
    点赞
  • 173
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值