使用Delphi開發Office Word插件


在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新聞組上的信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM