- 引言
- 一使用ActiveQt模块
- 二子线程中使用
- 三准备word模板
- 四代码
- 插入书签位置
- 批量插入
- 插入表格
- 方法一利用Range对象定位后插入表格
- 方法二利用bookmark定位后插入表格
- 五其他
- 参考
引言
项目中需要生成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)"); //退出worddelete 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文件中并没有这个bookmarkQAxObject* 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 serverCode : 5941Source : Microsoft WordDescription: ????????????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的问题