自动化技术在生成数据报告中的应用

2018-01-29 07:36孙海民姜学东计大杰于万国
实验室研究与探索 2017年12期
关键词:调用表格文档

孙海民, 姜学东, 计大杰, 于万国

(河北民族师范学院 数字与计算机科学学院,河北 承德 067000)

0 引 言

随着大数据产业发展,在可视化数据结果时,人们希望将分析结果如文本、图片、表格、数字等保存到文件,自动生成数据分析报告。笔者调查90%以上的数据分析管理系统存在这样需求。文献调研发现90%的MS Office自动化客户端项目都使用VBA开发,如《基于Excel VBA的渔具选择性分析SELECT模型实现》[1]《基于Excel的水泵性能试验数据处理的VBA开发》[2]《利用VBA技术和EndNote软件建立查新报告数据库》[3]。使用C++开发Microsoft Office自动化客户端研究很少,并且存在设计缺陷。以VC开发环境下生成Word文件为例,有人提出将数据分析结果写入临时文件,再通过VBA从临时文件中读取数据并写入Word中[4]。由于使用临时文件和VBA会造成应用程序工作效率降低,究其原因是人们对自动化(Automation)技术研究不足。C++作为主流软件开发语言在开发MS Office自动化客户端方面研究不足。另外,已有文献关于自动化技术和MS Office组件对象模型研究不够全面和深入。深入研究自动化技术、MS Office组件对象模型,将数据结果自动保存到文件中的技术具有现实意义。本文将结合理论知识和实践应用两个方面,提出一种基于自动化技术自动生成数据报告文件的解决方案。

1 自动化技术

1.1 COM技术

MS Office组件是基于自动化技术的,开发人员利用这项技术可以在应用程序中调用组件方法和服务,利用现有组件功能达到自己的目的[5]。例如在应用程序中调用Excel对象插入图表实现数据可视化。自动化技术中提供服务的部分称服务器(也称为自动化组件)例如Word、Excel等,调用自动化组件服务的应用程序称为客户端。自动化组件由通过COM接口为客户端提供服务,每个自动化对象都会对外公开COM接口,对客户端来说组件接口是已知的。一个COM接口包含了功能上相关的一组函数,客户端获得COM接口指针后,便可以调用自己所期望的函数以利用其功能。IUnknown是COM的基本接口,所有COM接口都从该接口继承。它有QueryInterface、AddRef和Release三个方法,第一个方法用来查询COM对象的其它接口,第二第三个方法用于对象引用计数。生存期控制和接口查询是IUnknown接口两个重要功能[6]。

1.2 自动化技术

自动化是基于IDispatch接口的COM,IDispatch接口继承IUnknown接口。自动化技术继承COM优点,简化COM底层细节,还提供一组专用于自动化的数据类型等[7]。自动化对象是实现IDispatch接口的COM对象,该接口包含GetIDsOfNames、GetTypeInfo、GetTypeInfoCount和Invoke 4个方法。通过GetTypeInfoCount方法可以判断对象是否提供类型信息,如果对象提供类型信息,客户端调用GetTypeInfo方法就可以获取到类型信息,如CLSID、接口ID、成员函数等。GetIDsOfNames方法的功能是根据名字返回方法或者属性的DISPID,客户端以DISPID调用Invoke方法从而获得对象提供的功能。图1所示一个自动化对象模型。

图1 自动化对象示例图

1.3 类型库

客户端在调用自动化组件功能时,必须获取自动化组件对象属性和方法的相关信息[8]。自动化技术使用类型库(tlb文件类型)来保存这些信息。除此之外,OCX、OlB、DLL和EXE等文件也可以保存类型信息。使用OLE Object Viewer可查看组件类型库信息。

1.4 调用方式

客户端调用自动化组件对象有早绑定和晚绑定两种方式。早绑定是在编译期间就确定了调用组件对象相关信息,如方法名、参数等,通过导入类型库来实现,是静态绑定。晚绑定是应用程序在运行时根据对象属性或方法名调用GetIDsOfNames方法获取DISPID,再调用接口的Invoke方法,是动态绑定。

1.5 事件通知

自动化组件能否将自身状态变化通知给客户端?例如当切换EXCEL工作表时,客户端能够感知到EXCEL组件对象的状态变化,以便做出相应处理,如保存当前工作表数据防止丢失。在自动化技术中通过事件通知(Events)来实现。

事件通知传出接口,它包含一组函数,每个函数对应一个事件。事件通知的接口实现是由客户端的捕获器来完成的。自动化技术中通过IConnectionPointContainer和IConnectionPoint接口以连接点的方式来实现事件通知和处理。第1个接口用于对连接点的管理,该接口FindConnectionPoint方法根据事件通知接口ID返回第2个接口指针,将事件通知与第2个接口关联起来。第2个接口的Advise方法将客户端的事件捕获器关联起来,这样就形成“事件通知-连接点-捕获器”的关联。当组件对象状态改变时,事件捕获器收到事件并进行处理。图2所示客户端处理自动化组件事件通知的过程。

2 MFC对自动化技术的支持

2.1 自动化对象开发

如果从头开始开发C++自动化对象不仅效率低而且非常繁琐[9][10]。VC++可以创建支持自动化特性的工程,该工程会维护一个idl文件,该文件记录了工程中所有自动化对象、接口及其属性方法等信息。利用MFC添加类型向导时,开发者可以指定“自动化”选项,从而创建自动化接口及从CCmdTarget类派生的自动化类。MFC封装了所有自动化对象所必须的一些代码,简化了开发自动化对象过程。

图2 自动化客户端处理组件事件通知时序图

2.2 自动化对象调用

MFC提供了COleDispatchDriver类实现对自动化对象IDispatch接口的处理。当用Class Wizard导入组件类型库时,Wizard自动创建组件接口的COleDispatchDriver包装类,组件接口的属性和方法被转换为该类的成员函数,其操作过程如下:

(1)在Class View视图右击,在级联菜点击“添加”,鼠标指向“类”。

(2)在“添加类”对话框左边窗格中,选择“Visual C++|MFC”选项,在右边窗格中选择“TypeLib中的MFC类”,点击“打开”按钮。

(3)在“从类型库添加类向导”对话框选择从“注册表”来源添加可用的类型库,在“可用的类型库”列表框中选择类型库,“接口”窗格就会显示该类型库的接口,双击接口则会自动创建接口对应的类名称及生成对应的头文件。图3使用类型库向导为应用程序添加Excel Object Library引用。

图3 VC++中导入类型库图

自动化技术中使用VARIANT数据类型传递数据,MFC提供了COleVariant类实现对VARIANT数据结构的封装。

3 Microsoft Office组件对象模型

MS Office提供了一个可编程对象集合,开发者可以通过可编程对象来调用Office组件功能[11]。这将极大加速应用程序开发,我们以Word为例讲解MS Office组件对象模型。

3.1 组件对象模型

MS Office组件对象以树状层级结构排列,Word的任何元素,如文档、表格、书签等都是对象。每个对象都有一个父对象(Application除外),包含多个子对象。图4所示,Word形成以Application对象为根节点的树状层级结构[12]。

图4 Word对象模型图

Application对象包含Documents集合对象,通过其Item属性就可以得到单个Document对象。通过Document对象又可以得到Range、Sections、Sentences和Paragraphs等对象。每个对象都有属性和方法,属性是对自动化对象的某种状态的描述,Document对象有段落、背景、保存等属性。方法是指自动化对象提供的服务,如Document对象Undo和Redo方法执行撤销和恢复功能。集合对象是一组同类对象的容器,通过枚举的方法可以得到该集合中的对象,图4背景为白色的对象都是集合对象。如Dialogs集合对象。通过Item方可以得到集合中的每个对象。

Word版本不同其对象模型有所差异,随着Word版本的不断提高,不断有新的对象加入到模型中[13]。例如,在Word2003中新增了Break(s)等对象。版本变化也会带来对象的属性和方法的会更新,如Word 2010版中Application对象放弃了MountVolume等方法和属性。在开发中使用OLE编程标识符(ProgID)创建自动化对象[14],Word使用“Application”作为编程标识符,PPT使用“PowerPoint.Application”作为编程标识符。

3.2 几个重要对象

3.2.1Application对象

Application表示Word应用程序,是其它所有对象的根。在这些成员对象中可以通过get_Application方法直接得到Application对象[15]。当用户启动了Word应用程序时,也就创建了Application对象,创建Application对象代码如下:

CApplication app;

app.CreateDispatch("Word.Application")));

下面代码演示Word退出时的函数调用过程,在代码中调用ReleaseDispatch方法释放资源。

CComVariant save(false),origFmt,doc;

app.Quit(&save,&origFmt,&doc);

app.ReleaseDispatch();

可以使用Application对象的属性和方法来设置和获取Word环境信息。例如下面代码将Word窗口可视化并设置为最大化。

app.putVisible(TRUE);

app.putWindowState(1);

3.2.2Document对象

新建Word文档时就创建了一个Document对象,该对象被添加到Documents集合中。Document对象Avtive方法用于设置对象为活动状态。调用对象Open和Add方法打开和创建的文档都具有活动文档属性。下面代码示意关闭文档。

CComVariant varSave(-2),varDoc(1),varRoute(FALSE);

doc.Close(&varSave,&varDoc,&varRoute);

Documents是Docment的集合对象,调用Add方法创建一个新的Document对象并加入该集合;Open方法打开一个Word文档并加入该集合;Item返回一个文档对象;Close方法关闭指定文档;Save方法保存所有的文档。

3.2.3Selection对象

操作Word文档主要是通过Selection对象来实现。Selection对象表示当前选择的区域,例如设置文本颜色时被选中的文本就是一个Selection对象。Selection对象始终存在于文档中,如果用户没有选择文本则它表示插入点,表1所示该对象的主要属性。

应用程序有且只能有一个活动的Selection对象。下面代码示意为所选中的每个段落添加矩形边框,

CPphs pahs = selection.get_Paragraphs();

CBorders borders = pahs.get_Borders();

borders.put_Enable(TRUE);

3.2.4Range对象

Range对象通过开始和结束字符的位置来来引用

表1 Selection对象常用属性

文档中某一连续区域。Word组件模型中多个对象具有Range属性。下面代码演示Range对象引用文档中第四段落,设置段落格式为右对齐且选中该段落。

CPphs pahs = doc.get_Paragraphs();

CPphs pah = pahs.Item(4);

CRange range = pah.get_Range();

CPahFormat pahFormat = pah.get_Format();

pahFormat.put_Alignment(2);

range.Select();

3.2.53个属性对象

Information、Type和Flag对象通常用于获取和设置对象的属性信息。Information对象返回Selection或range对象的有关信息。如页码、节、表格列号等。下面代码示意如何获得当前光标所处的行号。

CComVariant varLineNum=selection.get_Information(10);

Type对象用于返回Selection、Document、Window等对象的属性。Type的属性依对象不同而不相同。如Selection对象具有wdSelectionIP等属性值。开发人员通过操作对象属性而修改对象特征,下面代码将一个图片从嵌入型版式修改为浮于文字上方。

if(wdSelectionInlineShape==selection.get_Type()){

CnlineShapes inlineShapes=selection.get_InlineShapes();

CnlineShape inlineShape = inlineShapes.Item(1);

inlineShape.ConvertToShape();}

Flag属性仅用于Selection对象,该属性可读写,属性值包括wdSelStartActive等。当向一个Word文档输入文本时,若希望设置编辑为插入状态,代码示意如下:

if (wdSelOvertype&selection.get_Flags()){

selection.put_Flags(wdSelReplace);}

4 自动化客户端开发实例

我们以VC环境下开发Word(2003版)组件客户端为例,详细讲解自动化组件的客户端开发过程。开发情境如下:在一个油田井下测试数据分析平台中需要将数据分析结果保存到Word中,自动生成数据分析报告。数据分析报告包括封面、目录、正文部分(包括章节、正文、表格、图片、正文等)、页眉等。数据分析报告可以分为固定项目和插入项目两个部分。固定项目是指每次创建文件时不需要更改的部分,如封面格式、目录、页眉、不变的文本、正文格式、表格属性、图片属性、正文章节层级结构、插入图片的位置和表格的位置等。插入部分是指平台产生的数据、文本、图片、表格中的数据等,要利用Word组件对象的功能将这部分内容插入到指定位置。因此需要创建一个文档模版,设置封皮样式、页眉页脚、目录结构、插入字体格式、表格属性和图片属性等。生成Word报告时,创建Word组件对象客户端,打开文档模版并向其中添加数据,完成后另存文档。

4.1 编制Word报告模版

(1)封皮。设置报告题目“……报告”黑体一号字加粗,“单位:……”宋体一号字,“报告人:……”宋体一号字,“完成日期:……”宋体一号字,插入分页符。

(2)页眉页脚。添加页眉“……”,在页脚添加页码。

(3)目录。目录便于用户快速定位该报告的内容。在报告模版中预留目录空间,当完成报告后动态添加目录。在目录位置输入“目 录”设置宋体一号字,插入分页符。

(4)目录结构设置。根据报告的需要设置目录的格式,形成递进的层级结构。

(5)录入固定不变的内容。将每次生成Word报告都相同的内容包括文字、图片和表格等保存在模版中,并预留插入文本、图片的位置。在生成Word报告时,在预留的位置中添加数据处理结果包括文本、数字和图片等。

(6)设置表格和图片。在模版中添加表格并设置表格属性。这种方法不需要在VC中动态生成表格,开发人员只需要向表格内添加数据。如果表格行数不确定(大于2行),则只需要在模版中添加标题行和一个空行就可以了。在程序中根据具体情况动态添加表格行。在模版中预留插入图片的位置并设置格式,报告中插入的图片基本都是嵌入型版式且水平居中对齐,由程序添加图名称和表名称。

(7)设置模版密码。为增加模版安全性,为该模版设置密码保护,防止被意外修改。另外,开发人员根据具体情况决定是否进行页面设置等。

4.2 封装Word对象接口

为提高开发速度减少重复代码,在开发中对Word接口的包装类进行封装。由于在Word文档中几乎所有的操作都通过Selection对象来完成,所以我们将在Word中的所有操作都封装在一个CMySelection类中,该类主要实现以下功能:①插入符操作:移动插入符至文档开始结束位置、行首、行尾、向上下左右移动和换行。②字符串操作:查找、删除、插入和替换。③表格操作:创建表格、添加数据、删除表格和添加表标题。④图片操作:插入、删除图片和添加图标题;复制和粘贴。⑤创建目录。图5所示生成Word报告模块的静态类图,COperateMSWord类中组合了CMySelection、CAppliction和CDocuments类。在COperateMSWord类中按照生成Word报告文档内容的先后顺序依次声明方法,在这些方法中调用CMySelection类的有关方法实现对文本、数字、图片、表格、目录等内容的添加。

图5 操作Word对象类图

4.3 关键技术

(1)打开和保存文档。打开文档代码如下,该模版设置密码保护,所以在Open函数的参数中包含了模版的密码。为防止在生成报告过程中,用户更改插入符的位置,设置文档窗口为不可视状态。

if (FALSE == word.CreateDispatch(_T("Word.Application"))){

return;

}

word.put_Visible(FALSE);

m_docsWord = word.get_Documents();

CString strReportTem = GetExecutePath();

strReportTem += _T("Report-template.doc");

CComVariant varFile(strReportTem),varT(TRUE),varF(FALSE),varNull(_T("")),varFmt(0),varDire(0),varPwd(_T("shm"));

m_docsWord.Open(&varFile,&varF,&varF,&varF,&varPwd,&varNull,&varF,&varPwd,&varNull,&varFmt,&varF,&varT,&varT,&varDire,&varT,&varNull);

当报告生成后调用Document对象SaveAs方法将报告保存,同时释放Word对象资源,代码与前面相似不再给出。

(2)插入文字。CMySelection类实现了InsertStringAfter和InsertStringBefore两个插入字符串的方法。根据不同情况调用相应的方法在当前选定的字符串之前或者之后插入字符串。这两个函数都有两个参数,一个参数是用于确定插入字符串位置的待查找字符串,另一个参数是待插入的字符串。在这两个方法中,首先执行查找操作,将光标定位到插入字符的位置,然后调用Selection对象的InsertAfter或者InsertBefore方法插入字符串。CMySelection类的FindString函数中封装了Word的查找和替换操作,在Word对象模型中Find对象实现查找和替换功能。在FindString函数中首先调用Selection对象的get_Find方法得到Find对象,然后清除格式信息,最后调用Find对象的Execute方法执行查找操作。

CFind findWord = m_selWord.get_Find();

findWord.ClearFormatting();

CComVariant varFindText(strFind),varF(FALSE),varT(TRUE),varWrap(1),varNull;

findWord.Execute(&varFindText,&varT,&varT,&varF,&varF,&varF,&varT,&varWrap,&varF,&varF,&varNull,&varF, &varF, &varF, &varF);

用于确定插入字符串位置的字符串,在报告模版中必须具有唯一性。替代这种字符串查找的另外一种方式是使用书签。由于书签内容在编辑模版时不能清晰显示出来,不利于模版文档的维护,故在开发中未采用这种方法。Find对象实现查找和替换功能,通过Find对象可以得到Replacement对象,该对象是执行替换操作时的替换条件,为该对象赋值后(替换的字符串),该调用Find对象Excute方法执行替换操作。

(3)添加图片。CMySelection类InsertPicture方法实现向文档中插入图片功能。在模版中预留了插入图片的位置,在图片位置的正下方是该图片的名称。该函数首先调用FindString方法找到图片名称,然后调用MoveUp方法将插入符向上移动到插入图片的位置,然后调用AddPicture方法插入图片,最后为图片添加题注。报告文档中的图片都是嵌入型的,在Word对象模型中InlineShape对象表示嵌入型图片。在AddPicture方法中通过Selection对象得到InlineShapes集合对象,然后调用该对象AddPicture方法将图片添加到文档中。

CnlineShapes inlineShapes=m_selWord.get_InlineShapes();

CComVariant varLinkToFile(false),varSave (true);

CRange range = m_selWord.get_Range();

inlineShapes.AddPicture(szFilePath,&varLinkToFile, &varSave, &CComVariant(range));

使用题注的方法为图片和表格命名是很有实用价值的,在CMySelection类中也封装了插入题注的功能。在该类中首先添加图标签,然后调用Selection对象的InsertCaption方法为插入的图添加题注。

CCaptionLabels captionLabels = appWord.get_CaptionLabels();

captionLabels.Add(_T("图"));

CComVariant varLab(T("图")),varTle(_T("")),varTleAuTxt(""),varPos(wdCaptionPositionBelow),varEx(false);

selection.InsertCaption(&varLab,&varTle,&varTleAuTxt,&varPos,&varEx);

(4)添加表格数据。在报告模版中已经插入了表格和表名称(在表格的正上方),所以首先调用FindString函数找到表名称并为该表加入题注,然后将光标下移到需要插入数据的位置,插入数据。这时需要判断当前的插入符是否在表格内,必要时还需要判断插入符所在的行和列。通过Selection对象的Information属性实现此项功能。

CComVariant varBInTle(true);

i(varBInTl==election.get_Information(12)){

CComVariant varCol=selection.get_Information(16);

CComVariant varRow=selection.get_Information(13);}

如果插入到表格中数据数量不确定,则该表格行数就不确定。这就要求程序根据数据长度动态添加表格行。在程序中使用MFC的CStringList类保存插入的数据,该类是一个由字符串构成的链表。当插入符移动到表格右下角的单元格时,按下Tab键会在最后一行下面增加一空行。下面代码为表格增加一空行。

CComVariant

varUnit(wdCell),varCut(1),varN;

selection.MoveRight(&varUnit,&varCut,&varN);

使用这种方法插入表格行的优点是,当插入空行后,插入符会自动移动到该行的第一个单元格,这样就可以继续插入数据。

(5)创建目录。创建目录的方法比较简单,Word中TablesOfContents对象的Add方法实现该功能。该函数参数比较多包括Appliction、Document、Range等对象。创建目录时选择使用点填充目录项目与页码之间空白,并设置目录内容按照级别递增索引,代码如下所示。

CApplication app = word.get_Application();

CDoc actDoc=app.get_ActiveDocument();

CTablesOfContents tabsOfCtets = actDoc.get_TablesOfContents();

CRange range = m_selWord.get_Range();

CComVariant varF(false),varT(true),varUp(1),varLow(3),varNull;

CTableOfContents table= tabsOfCtets.Add(range,&varT,&varUp,&varLow,&varF,&varNul,&varT,&varT,&varNul,&varT,&varT,&varT);

table.put_TabLeader(1);

tabsOfCtets.put_Format(0);

由于Word报告中存在格式相同的内容,例如表格1、表格2……,这些表格的格式都一样,标题行也一样,只是填充的内容不同。通过调用selection对象Copy和Paste/PasteAndFormat方法执行复制和粘贴操作。这样解决了在报告中格式相同而内容不同的问题。由于创建的Word报告内容比较多时间长,为了告知用户当前创建报告的进度,我们使用一个进度条显示当前生成报告的状态。

5 结 语

本文以Microsoft Office组件为开发对象,提出一种基于自动化技术生成数据报告文件的解决方案,详细讲解了自动化技术知识,MFC对自动化技术的支持,Microsoft Office组件对象模型,最后以一个项目详细说明了VC环境下开发自动化组件客户端的过程,希望本文能够给人们提供一个很好的帮助。

[1] 金宇锋,张 健.基于Excel VBA的渔具选择性分析SELECT模型实现[J].实验室研究与探索,2014(3):154-158.

[2] 汤 跃,赵 坤,许文博.基于Excel的水泵性能试验数据处理的VBA开发[J].排灌机械工程学报,2011(2):123-126.

[3] 王 磊,张仁琼.利用VBA技术和EndNote软件建立查新报告数据库[J].现代情报,2015(8):131-136,140.

[4] 朱 敏,沈同圣,王学伟.VC++与VBA结合实现复杂报表[J]. 计算机应用与软件,2005(2):42-43+101.

[5] 叶 明,张 诤.基于C#.NET的Word报告生成功能开发[J].计算机工程与应用,2008(9):104-106.

[6] 潘爱民.COM原理与应用[M].北京:清华大学出版社,1999:334.

[7] 朱 敏,沈同圣.VC++与VBA结合实现复杂报表[J].计算机应用与软件,2005(2):42.

[8] 汤克明,陈 崚.Word自动阅卷系统的设计与实现[J]. 计算机工程与应用,2008(35):69-72.

[9] 孔令彦,董蓬勃,姜青香.使用Visual Basic操纵Microsoft Word对象生成报表文档[J].计算机工程与应用,2003(36):115-117.

[10] 赵宏亮,杨鹤标.面向领域的语义搜索引擎的应用研究[J].计算机工程与设计,2012,(05):1801-1805.

[11] 冉 沛,杨吉云,谭金勇.一种新的Word电子文档完整性保护方案[J].计算机工程与应用,2013(13):76-79+148.

[12] MSDN.Office development[EB/OL].https://msdn.microsoft.com/en-us/library/fp161347.aspx, 2016-12-1.

[13] 朱 敏,方登建,王 哲. Word模板数据自校验设计与信息提取技术[J]. 实验室研究与探索,2012(3):75-78.

[14] 杨德明,郭 盛. 基于Word文档的数据隐藏方法[J].计算机应用与软件,2015(5):314-318.

[15] 高丽萍,郭栋彬,郑博文. Co-Word中图文混排的文档一致性研究[J].计算机应用研究,2017(11):1-10.

猜你喜欢
调用表格文档
《现代临床医学》来稿表格要求
浅谈Matlab与Word文档的应用接口
《现代临床医学》来稿表格要求
有人一声不吭向你扔了个文档
统计表格的要求
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
基于系统调用的恶意软件检测技术研究
基于RI码计算的Word复制文档鉴别
Persistence of the reproductive toxicity of chlorpiryphos-ethyl in male Wistar rat