使用Delphi开发Office Word插件 |
日期:2005年3月4日 作者:转自哈巴狗的小窝 人气:<script src="../Hits.asp?ArticleID=416" type="text/javascript"> </script> 69 查看:[大字体 中字体 小字体] |
<script src="../js/article_zt.js" type="text/javascript"> </script>企商在线老牌虚拟主机商 购主机送鼠标 送U盘送30G移动硬盘! |
<script src="../js/softview_468x60.js" type="text/javascript"> </script> id="google_468" marginwidth="0" marginheight="0" src="../_google_468.htm" frameborder="0" width="468" scrolling="no" height="60"> |
在Office 2000中提供了基于COM的插件开发框架,这使得我们可以利用Delphi来扩展Office的功能。
在Delphi 3,4中编写基于COM的插件,我们需要自己创建COM接口的封装类,更糟糕的是要想支持事件的话还需要使用连接点(connection points)对象来实现事件回调,这是非常麻烦的。但在Delphi 5中这一切就变得非常轻松了,Delphi 5的类型库引入工具提供了/L+的开关,可以自动为我们生成封装好的OLE Server。这下子再也没有什么好抱怨的了。 Office 2000 插件框架 在Microsoft'的网站上,知识库文章(Knowledge Base article Q230689)中有一篇:Office 2000 COM Add-In Written in Visual C++ 。文章中提供了一个例子(http://support.microsoft.com/download/support/mslfiles/ COMADDIN.EXE)。这篇文章详细地描述了插件框架中的COM接口。仔细研究一下C++代码就可以了解如何编写Office 2000插件。 Office 2000插件其实就是一个实现了IDTExtensibility2接口的自动化对象。IDTExtensibility2 接口相当简单,插件需要实现接口定义的全部5个函数: OnConnection:当应用程序连接到插件时会调用这个函数。插件在函数中接收下列初始化信息——应用程序对象模型进入点的指针,连接模式(是手工加入还是通过命令行载入), 应用程序的对象模型指针和用户自定义的信息。 OnDisconnection:当应用程序断开插件时被调用,插件应该在这里清除先前分配的资源,删除它添加到应用程序的界面元素。 OnStartupComplete:这个函数是当应用程序自动启动插件时被调用的。调用时,其他的插件都已经被加载到了内存,这时可以同其他插件进行通信。这个函数还适合添加用户界面元素。 OnBeginShutdown:当应用程序准备关闭并将要断开插件时会被调用,这时插件应该停止接收用户输入。 OnAddInsUpdate:当注册的插件列表被改变后会被调用。如果我们的插件不依赖于其他插件,这个函数可以为空。 接口、类型库和常数 创建插件前,我们需要引入COM对象的接口类型库。这里使用Delphi 5带的TlibImp.exe (Delphi5/Bin目录下)来引入类型库。新版的TlibImp.exe支持新的/L+开关,可以自动创建一个OLE Server的Delphi封装。IDTExtensibility2接口是在MSADDNDR.DLL文件中声明的,位于/Program Files/Common Files/Designer/ 目录下。调用TLIBIMP/L+/Program Files/Common Files/ Designer/MSADDNDR.DLL会生成AddInDesignerObjects_TLB.pas 和 AddInDesignerObjects_TLB.dcr两个文件。在项目的uses部分加上对上面文件的引用以便使用接口。clause of our project to gain access to the interface.使用时注意:TLIBIMP重命名接口为_IDTExtensibility2。 本文中将使用Word 2000作为例子,如果想编写Outlook、Excel或其他Office程序的插件需要引入相应特定的类型库。比如Word的类型库是定义在/Program Files/Microsoft Office/Office/MSWORD9.OLB文件中。类似的,Excel、Access和OutLook类型库分别定义在EXCEL9.OLB、MSACC9.OLB和MSOUTL9.OLB文件中。引入的接口生成在Office_TLB.pas和Word_TLB.pas单元中。 注意:Office 2000的插件无法工作在Office 97的应用程序中。 最简单的插件 现在让我们来实现一个最简单的插件,它只实现了IDTExtensibility2接口而没有实现任何比较有意义的功能,但对于演示如何实现插件是一个很好的开始。 插件可以以进程内或进程外COM服务器的形式实现,在本文中,我们创建的是进程内COM服务器。在Delphi中,选择菜单File | New命令,然后创建一个ActiveX Library,保存生成的文件,再创建一个自动化对象(Automation Object),类名定义为AddIn,把实现单元保存为AddInMain.pas。在AddInMain.pas单元的uses部分添加对AddinDesignerObjects_TLB,Word_TLB和Office_TLB单元的引用。最后添加 IDTExtensibility2 接口到类定义部分定义类要实现的接口。类定义如下: type TAddIn = class(TAutoObject, IAddIn, IDTExtensibility2) ... 在类声明的protected部分,添加IDTExtensibility2 接口声明的方法定义,代码示意如下: // IDTExtensibility2 methods procedure OnConnection(const Application: IDispatch; ConnectMode: ext_ConnectMode; const AddInInst: IDispatch; var custom: PSafeArray); safecall; procedure OnDisconnection(RemoveMode: ext_DisconnectMode; var custom: PSafeArray); safecall; procedure OnAddInsUpdate(var custom: PSafeArray); safecall; procedure OnStartupComplete(var custom: PSafeArray); safecall; procedure OnBeginShutdown(var custom: PSafeArray); safecall; 使用快捷键[Ctrl][Shift][C]来完成类定义,并添加方法的实现部分的框架到单元中。为了测试插件,可添加下面代码到OnConnection方法中: ShowMessage('连接到' + WordApp.Name); 添加下面代码到OnDisconnection方法的实现部分: ShowMessage('断开插件'); 这样就完成了一个最简单的插件了,接下来就是编译并注册插件到Word中去。 注册Office插件 同其他COM对象一样,一个Office插件必须在系统中注册后才能使用。在Delphi中选择Run | Register ActiveX Server菜单命令,就可以注册我们刚才创建的插件。除了标准的COM注册,还需要进行Office 相关的注册,这需要在注册表中创建一个新的键值: HKEY_CURRENT_USER/Software/Microsoft/Office/ <AppName>/Addins/<AddInProgID> <AppName>就是插件宿主应用程序的名字(这里是Word),<AddInProgID>是自动化对象的名字(这里是DIWordAddIn.AddIn,ActiveX library和类名的组合)。 HKEY_CURRENT_USER / Software / Microsoft / Office / Word / Addins/DIWord AddIn.AddIn 我们还需要在这个键值下创建几个值:一个DWORD类型的名为LoadBehavior的值决定插件是如何加载及被应用程序调用的。在本文中我们设定它为3–相当于1和2的结合就是应用程序连接插件并在启动时自动加载。值的意义列在表1.2中,各种值可以相互组合。 表1.2 值 意 义 $0 断开,不加载 $1 连接,加载 $2 自动启动加载 $8 只有当用户请求时才加载 $16 只在下次程序启动时加载一次 还有一些其他的值可以出现在注册表键值下,比如定义出现在应用程序COM管理器对话框中的名字,以及设定是否可以从命令行激活插件。 Office 2000用户界面 Office应用程序共享一组通用的用户界面元素对象、菜单条、工具条通用控件(比如工具条按钮和组合编辑框)以及Office小助手。 此前引入的Word类型库就包括了这些通用对象的类型库,但是Delphi引入时并没有像通常那样建立一个封装好的OLE Server,我们不得不手工创建一个Office公开的CommandBarButton对象的Delphi封装。这个对象对应于Office应用程序的一个简单的菜单项或工具条按钮。 对大多数的Microsoft的应用程序来说,Application对象代表对象模型的切入点。Office Application类提供了对CommandBars属性的引用。CommandBar对象包括工具条、浮动工具条和菜单。Office对象模型允许我们创建或更新已有的CommandBars对象。Office_TLB.pas单元包含了ICommandBar接口,它可以被用来修改CommandBar对象。 CommandBar有一个Controls集合属性,对应于一组CommandBarControl控件。CommandBarControl控件对应于放置在工具条上的控件,比如一个CommandBarButton对应一个简单的工具条按钮(或菜单项),CommandBarCombo控件对应组合编辑框,CommandBarPopup对应于下拉菜单和CommandBarActiveX对应于ActiveX控制。 在Office_TLB.pas单元中,除了ICommandBarButton接口,还有一个ICommandBarButtonEvents接口用来提供对工具条上控件的事件支持。事件的支持通常是通过连接点、事件接收连接到可连接对象来实现。但这比较麻烦,我们还可以通过更简单的办法来实现事件支持。检查一下Delphi在word_tlb.pas单元创建的TWordApplication的实现代码可以发现Delphi封装了每一个可连接对象,自动实现了事件接收机制。这个单元可以作为一个范本用来创建自定义的对接口对象的封装。 BtnSvr.pas单元包含了一个手工创建的Delphi封装。除了按标准的Delphi属性方式实现了CommandBarButton对象的属性外,还实现了InitServerData、InvokeEvent、Connect、ConnectTo和Disconnect方法。可以注意到这部分完全是模仿TWordApplication实现部分编写的CommandBarButton事件实现。主要就是在InitServerData方法中定义服务器数据。根据Office_TLB.pas中不同的接口GUID,定义一个CommandBarButton接口的内部的接口Fintf,设定InvokeEvent方法来激活基于定义在事件接口部分的DispID的Delphi事件支持。最后,Connect、ConnectTo和Disconnect方法设定Fintf给需要的接口并接收相应的事件。 定义在BtnSvr.pas单元中的Delphi封装类命名为TButtonServer。它需要从TOleServer对象继承以便支持事件处理。 同应用程序连接 有了工具条按钮封装类后,接下来要声明一个TWordApplication域来保存对Word Application对象的引用。此外还需要为新的工具条定义一个接口指针以及两个域使用新的TButtonServer类来保存我们要创建的新的工具条按钮和菜单项。 在插件类的private部分添加: FWordApp : TWordApplication; DICommandBar : CommandBar; DIBtn : TButtonServer; DIMenu : TButtonServer; 在OnConnection方法中,保存应用程序指针: var WA : Word_TLB._Application; begin FWordApp := TWordApplication.Create(nil); WA := Application as Word_TLB._Application; WordApp.ConnectTo(WA); ………………………….. TWordApplication是Delphi 5中带的Server组件,ConnectTo 方法是用来连接插件和Word提供的接口。因为TWordApplication 把接口事件映射成了Delphi事件,我们可以直接使用标准的Delphi语法来设定事件处理过程。示意如下: WordApp.OnEventX := EventXHandler; 比如我们如果想在Word的选区发生改变时实现某项功能,就可以设定OnWindowSelectionChange 事件。 插件如何创建新的工具条、按钮和菜单 在创建新的工具条和按钮前,需要为按钮的OnClick过程先创建事件处理函数,下面就是简单的处理函数例子: procedure TAddIn.TestClick(const Ctrl: OleVariant; var CancelDefault: OleVariant); begin ShowMessage('有人点我了!'); CancelDefault := True; end; CancelDefault参数用来设定是否替代缺省的菜单或工具条按钮的处理过程。这里不需要设定这个参数,因为我们将在插件中创建一个新的按钮。插件注册为在程序启动时被加载,所以OnStartupComplete方法一定会被调用,用这个方法创建用户界面元素是比较合适的。这里定义BtnIntf为CommandBarControl接口类型(要创建的CommandBarButton的父类接口)。接下来的任务是确定自定义的工具条是否已经被创建了 DICommandBar := nil; for i := 1 to WordApp.CommandBars.Count do if (WordApp.CommandBars.Item[i].Name ='Delphi') then DICommandBar := WordApp.CommandBars.Item[i]; // 确定是否已经注册了命令条 if (not Assigned(DICommandBar)) then begin DICommandBar:= WordApp.CommandBars.Add('Delphi',EmptyParam,EmptyParam,EmptyParam); DICommandBar.Set_Protection(msoBarNoCustomize) end; 先给工具条起一个唯一的名字“Delphi”,然后在启动时检查工具条是否已经被创建了。如果是的话就把它赋值给DICommandBar,否则调用Word的CommandBars属性的Add方法创建一个新的工具条。接着给工具条添加msoBarNoCustomize的保护,这可以防止用户添加或删除工具条上的按钮。这时DICommandBar指向一个有效的工具条,我们可以从接口的Controls集合中获得工具条按钮接口指针。如果工具条上没有控件,就创建一个新的按钮。 if (DICommandBar.Controls.Count > 0) then BtnIntf := DICommandBar.Controls.Item[1] else BtnIntf := DICommandBar.Controls.Add(msoControlButton, EmptyParam, EmptyParam, EmptyParam, EmptyParam); 注意:集合中第一项是以1为底的,而不像Delphi中那样通常以0为底。现在我们获得了需要的工具条按钮接口,然后要创建一个基于按钮接口的TButtonServer 类封装。 DIBtn := TButtonServer.Create(nil); DIBtn.ConnectTo(BtnIntf as _CommandBarButton); DIBtn.Caption := 'Delphi Test'; DIBtn.Style := msoButtonCaption; DIBtn.Visible := True; DIBtn.OnClick := TestClick; 这里使用ConnectTo 方法连接按钮的事件并设定先前创建的OnClick事件处理过程。最后,要确认使工具条可见。 DICommandBar.Set_Visible(True); TLIBIMP程序创建了一个只读的而非可读写的工具条Visible 属性,但可以使用Set_Visible 方法来设定显示属性(不能生成可读写的属性可能是TLIBIMP的bug)。添加新的菜单项类似于前面,首先创建菜单的OnClick事件处理函数,下面这个过程遍历被选文本的第一段,并设定其边框样式: procedure TAddIn.MenuClick(const Ctrl: OleVariant; var CancelDefault: OleVariant); var Sel : Word_TLB.Selection; Par : Word_TLB.Paragraph; begin Sel := WordApp.ActiveWindow.Selection; if (Sel.Type_ in [wdSelectionNormal, wdSelectionIP]) then begin Par := Sel.Paragraphs.Item(1); if (Par.Borders.OutsideLineStyle < wdLineStyleInset) then Par.Borders.OutsideLineStyle := 1 + Par.Borders.OutsideLineStyle else Par.Borders.OutsideLineStyle := wdLineStyleNone; end; end; 在OnStartupComplete方法中,添加下面的代码来获得工具菜单的接口指针,查找自定义的的菜单项,如果没有就创建新的,然后设定它的OnClick事件: ToolsBar := WordApp.CommandBars['Tools']; MenuIntf := ToolsBar.FindControl(EmptyParam, EmptyParam, 'DIMenu', EmptyParam, EmptyParam); if (not Assigned(MenuIntf)) then MenuIntf := ToolsBar.Controls.Add(msoControlButton, EmptyParam, EmptyParam, EmptyParam, EmptyParam); DIMenu := TButtonServer.Create(nil); DIMenu.ConnectTo(MenuIntf as _CommandBarButton); DIMenu.Caption := 'Delp&hi Menu'; DIMenu.ShortcutText := ''; 图1.34 DIMenu.Tag := 'DIMenu'; DIMenu.Visible := True; DIMenu.OnClick := MenuClick; CommandBar接口的FindControl方法使用唯一的标识来查找菜单项,如果找到了控件就赋值给 MenuIntf,如果没有找到就创建一个新的菜单项。图1.34显示了自定义的工具条。 清理资源 注意应该在OnBeginShutdown 方法中清理用户界面元素: if (Assigned(DIBtn)) then begin DIBtn.Free; DIBtn := nil; end; if (Assigned(DIMenu)) then begin DIMenu.Free; DIMenu := nil; end; if (Assigned(DICommandBar)) then begin DICommandBar.Delete; DICommandBar := nil; end; 因为插件的框架是通用的,我们可以将同样的OLE Server DLL用于多个应用程序,方法就是确定将激活插件的应用程序,并使用合适的对象模型。最简单的判断方法是在OnConnection中把应用程序的IDispatch的接口指针赋值给一个OleVariant变量,然后使用相应的Name 属性来确定相应的程序: var AppVar : OleVariant; begin AppVar := Application; if (AppVar.Name = 'Outlook') then begin ... end else if (AppVar.Name = 'Microsoft Word') then begin ... end else ... 最后,要想获得关于Office开发和Office 2000插件创建更详细的资料,可以查阅microsoft.public.officedev新闻组上的信息。 (出处:http://hubdog.csdn.net/Hubdog/word.htm) |
使用Delphi开发Office Word插件
最新推荐文章于 2024-09-02 11:46:21 发布