天天看點

Office2000下内部COM插件的程式設計實作

Office2000下内部COM插件的程式設計實作

譯者:徐景周

下載下傳示例源代碼

簡介

你也許曾在Office2000下的Word2000、Access2000、Excel2000、PowerPoint2000等軟體中的工具條或菜單條資源中,看到一些其它軟體加入的新的自定義工具條按鈕或菜單條,當點選它們時,會有其不同的響應發生。下面,讓我們也來實作這些功能,需要說明的是,在這裡我們不用VB/VBA來實作它,而是用VC6中所帶ATL(活動模闆庫)3.0來開發具有這種效果的Office2000内部COM插件。在Office2000中,不管是Word2000、Access2000、Excel120000、PowerPoint2000還是Outlook2000等,它們COM插件的程式設計方法及步驟都是極其相似的(除系統資料庫中鍵值及導入相應類型庫不同外)。

基礎知識

一個Office2000下的内部COM插件必須實作一個_IDTExtensibility2派發接口,_IDTExtensibility2派發接口被定義在MSADDin Designer類型庫(MSADDNDR.dll/MSADDNDR.tlb)中,通常位于<盤符>/Program Files/Common Files/Designer下。_IDTExtensibility2接口中必須實作下面五個接口函數(一般隻需編寫OnConnection和OnDisconnection中代碼),分别如下:

1. OnConnection: 裝載插件到記憶體時處理(可以通過自動化在程式啟動時自動裝載插件)。

2. OnDisconnection: 從記憶體中缷載插件時處理。

3. OnAddinsUpdate: COM插件改變時處理。

4. OnStartupComplete: 當應用程式啟動時插件剛裝載完成時處理。

5. OnBeginShutdown: 當應用程式關閉時插件剛缷載完成時處理。

注冊插件

隻有在正确注冊了相應應用程式的内部COM插件時,才能被其應用程式加載上。需要在系統資料庫中建立以下鍵值:

HKEY_CURRENT_USER/Software/Microsoft/Office/<TheOfficeApp>/Addins/<ProgID>

其中,TheOfficeApp表示相應程式名,如:Word、Outlook等,ProgID表示内部COM插件程式的唯一辨別符的字元串表示形式,如:Outlook2000Addin.Addin等。

ProgID鍵值下主要建立以下四個鍵值:

1. FriendlyName: 字元串類型,插件的名稱,将在相應程式的COM加載對話框中看到。

2. Description: 字元串類型,插件的描述資訊。

3. LoadBehavior: DWORD類型,決定插件将以什麼形式被裝載。當其值為0x03時,為應用程式裝載時被自動裝載(一般使用此值)、當其值為0x08時,為使用者控制激活裝載。

4. CommandLineSafe: DWORD類型,指令行方式,可以設定為0x01(真)或0x00(假)。

其它鍵值的完整描述可參看最新MSDN。

具體實作

下面,我們将以建立一個Outlook2000的内部COM插件為示例,向你一步步的展現如何最小化的建立一個Office2000的内部插件的全過程。效果圖如下所示:

Office2000下内部COM插件的程式設計實作

打開VC6.0,在建立工程中選中ATL COM Appwizard,在右側工程名中輸入OutlAddin,點選下一步,接受預設選項Dynamic Link Library(DLL)不變,可以選中下面的Allow merging of proxy-stub code(允許合并代理/占位)複選框選項,點選Finish(完成)按鈕完成工程建立。

接着,選取菜單Insert->New ATL Objec項,在彈出的ATL對象向導對話框中選中相應Objects對應右側的Simple Object選項,點選下一步,在彈出的對話框中ShortName中輸入OutlookAddin,如果需要的話,還可以在Attributes(屬性頁)中選中Support ISupportErrorInfo複選框選項,點OK完成插入ATL對象。

接着通過導入類型庫來實作_IDTExtensibility2接口,編釋好上面所建工程後,在ClassView中的COutlookAddin類上點滑鼠右鍵,在彈出的右鍵菜單中選Implement Interface項。在彈出的實作接口對話框中點選Add Typelib,在彈出的Browse Type Libraries對話框中,向下滾動選取Microsoft Add-in Designer(1.0)子項,點OK按鈕。在彈出的接口清單對話框中選中_IDTExtensibility2接口,點OK按鈕完成導入。系統會自動為你生成空的上面所提到的五個所需接口函數。

接着注冊編譯好的插件,在FileView->Resource Files中,打開OutlookAddin.rgs注冊檔案,在該檔案的最下面加入下面新的内部插件注冊碼:

// 新增Outlook2k内部插件注冊鍵值
HKCU
{
  Software
  {
    Microsoft
    {
      Office
      {
        Outlook
        {
          Addins
          {
            ''OutlAddin.OutlookAddin''
            {
            	val FriendlyName = s ''Outlook2000插件''
             	val Description = s ''使用ATL開發的Outlook2000的插件''
             	val LoadBehavior = d ''00000003''
             	val CommandLineSafe = d ''00000000'' 
            }
          }
        }
      }
    }
  }
}
      

編譯此工程,如果注冊正确的話,将可以在Outlook中COM附加元件的插件對話框中看到它的相應名稱。在Office2000中加載或解除安裝COM插件,一般可以按下面步驟進行:

1. 如果已經将“COM 附加元件”指令添加到了“工具”菜單,請跳到第 6 步。

2. 單擊“工具”菜單中的“自定義”指令,然後單擊“指令”頁籤。

3. 在“類别”框中,選擇“工具”。

4. 将“COM 附加元件”從“指令”框拖動到“工具”菜單。當“工具”菜單顯示菜單指令時,将滑鼠指針指向希望“COM 附加元件”指令出現在菜單上的位置,然後釋放滑鼠按鈕。

5. 單擊“關閉”按鈕。

6. 單擊“工具”菜單中的“COM 附加元件”指令,并執行下列操作之一:

● 要添加附加元件,請選中“可使用的附加元件”清單中附加元件名稱旁邊的複選框。如果所需的附加元件不在“可使用的附加元件”清單中,請單擊“添加”按鈕,找到要添加的附加元件,然後單擊“确定”按鈕。

● 要從記憶體中解除安裝附加元件,但希望在清單中保持其名稱,請清除附加元件名稱旁邊的複選框。

● 若要從清單中删除附加元件的同時,将其從系統資料庫檔案中注冊的附加元件清單中删除,請選擇附加元件的名稱,然後單擊“删除”按鈕。

在Office應用程式中,盡管菜單和工具欄按鈕看上去不太一樣,但實質上它們是相同類型的對象。CommandBars集合包含程式中的所有指令條,如:工具條和菜單條。每一個CommandBars集合都有一個CommandBar對象和它對應,CommandBar 對象可以包含其它的 CommandBar 對象,這些對象是作為按鈕或菜單指令來用的。每一個CommandBar都将通過CommandBarControls 對象被引用,CommandBarControls又可以包含一組CommandBarControl對象。每一個CommandBarControl可以包含一個CommandBar對象,并可以通過它來存取控件屬性。每一個CommandBarControl對象,實際是對應CommandBarControls中的控件集合。CommandBarControl可以有三種表現形式:

● 彈出式(CommandBarPopup):相當于菜單條的一個菜單項。

● 組合框(CommandBarComboBox):類似于工具條中組合框控件。它包括一個工具欄和緊接着工具欄的一個下拉箭頭。單擊該按鈕,将顯示出更多的帶圖示的菜單指令。

● 按鈕(CommandBarButton):相當于标準的工具欄按鈕,即帶有圖示的按鈕。

在下面的示例程式中,我們将在Outlook2K中建立一個工具條并在其上添加二個按鈕,并且在其菜單“工具”中建立一個菜單條,這些操作都可以在OnConnection接口函數中完成。

首先,我們需要在工程中導入Office和Outlook類型庫,可以在Stdafx.h檔案中加入下面語句(注意:其中路徑可根據Office所裝路徑自行設定):

// 導入工程所需Office2K及Outlook2K類型庫
#import "e:/Program Files/Microsoft Office/Office/mso9.dll" rename_namespace("Office"), named_guids
using namespace Office;

#import "e:/Program Files/Microsoft Office/Office/MSOUTL9.olb" rename_namespace("Outlook"), 
raw_interfaces_only, named_guids 
using namespace Outlook;
      

其次,讓我們來在Outlook中建立一個工具條,并且在其上添加兩個按鈕。

代碼如下:

// 裝卸插件時處理
STDMETHOD(OnConnection)(IDispatch * Application,
			ext_ConnectMode ConnectMode,
			IDispatch * AddInInst,
			SAFEARRAY * * custom)
{
		CComPtr < Office::_CommandBars> spCmdBars; 
		
		// Outlook應用接口_Application
		CComQIPtr <Outlook::_Application> spApp(Application); 
		ATLASSERT(spApp);

		// 擷取CommandBars接口
		CComPtr<Outlook::_Explorer> spExplorer; 	
		spApp->ActiveExplorer(&spExplorer);
		HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);
		if(FAILED(hr))
			return hr;
		ATLASSERT(spCmdBars);

		// 新增一個工具條及其上兩個位圖按鈕
		CComVariant vName("新增Outlook2K工具條插件");
		CComPtr <Office::CommandBar> spNewCmdBar;
		
		// 新增工具條位置
		CComVariant vPos(1); 
		
		CComVariant vTemp(VARIANT_TRUE); // 臨時		
		CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);			
		// 用Add方法在指定位置新增一工具條并讓spNewCmdBar指向它
		spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
		
		// 擷取新增工具條的CommandBarControls,進而在其上添加按鈕
		CComPtr < Office::CommandBarControls> spBarControls;
		spBarControls = spNewCmdBar->GetControls();
		ATLASSERT(spBarControls);
		
		//MsoControlType::msoControlButton = 1
		CComVariant vToolBarType(1);
		//顯示工具條
		CComVariant vShow(VARIANT_TRUE);
		
		CComPtr < Office::CommandBarControl> spNewBar; 
		CComPtr < Office::CommandBarControl> spNewBar2; 
		
		// 用CommandBarControls中的Add方法新增第一個按鈕,并讓spNewBar指向它
		spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow); 
		ATLASSERT(spNewBar);
		// 用CommandBarControls中的Add方法新增第二個按鈕,并讓spNewBar2指向它
		spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
		ATLASSERT(spNewBar2);
		
		// 為每一個按鈕指定_CommandBarButton接口,從面可以指定按鈕的顯示風格等
		CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);
		CComQIPtr < Office::_CommandBarButton> spCmdButton2(spNewBar2);
		
		ATLASSERT(spCmdButton);
		ATLASSERT(spCmdButton2);
		
		// 設定位圖按鈕風格,位圖為32x32大小,将其放入剪切闆中用PasteFace()貼在指定按鈕上
		HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
			MAKEINTRESOURCE(IDB_BITMAP),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
		
		::OpenClipboard(NULL);
		::EmptyClipboard();
		::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
		::CloseClipboard();
		::DeleteObject(hBmp);		

		// 粘貼前設定顯示風格
		spCmdButton->PutStyle(Office::msoButtonIconAndCaption);
		
		hr = spCmdButton->PasteFace();
		if (FAILED(hr))
			return hr;
		
		spCmdButton->PutVisible(VARIANT_TRUE); 
		spCmdButton->PutCaption(OLESTR("按鈕1")); 
		spCmdButton->PutEnabled(VARIANT_TRUE);
		spCmdButton->PutTooltipText(OLESTR("按鈕1提示資訊")); 
		spCmdButton->PutTag(OLESTR("按鈕1标志")); 
		
		// 顯示新增工具條
		spNewCmdBar->PutVisible(VARIANT_TRUE); 
		
		// 設定第二個工具條按鈕風格
		spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
		
		// 第二個按鈕指定位圖為Outlook2K中預先定義的位圖
		spCmdButton2->PutFaceId(1760);  
		
		spCmdButton2->PutVisible(VARIANT_TRUE); 
		spCmdButton2->PutCaption(OLESTR("按鈕2")); 
		spCmdButton2->PutEnabled(VARIANT_TRUE);
		spCmdButton2->PutTooltipText(OLESTR("按鈕2提示資訊")); 
		spCmdButton2->PutTag(OLESTR("按鈕2标志"));
		spCmdButton2->PutVisible(VARIANT_TRUE);
		
		m_spButton = spCmdButton;
		m_spButton2 = spCmdButton2;
		……
      
接着,讓我們在菜單"工具"中建立一個菜單條。 代碼如下:
_bstr_t bstrNewMenuText(OLESTR("新增菜單條"));
CComPtr < Office::CommandBarControls> spCmdCtrls;
CComPtr < Office::CommandBarControls> spCmdBarCtrls; 
CComPtr < Office::CommandBarPopup> spCmdPopup;
CComPtr < Office::CommandBarControl> spCmdCtrl;
		
CComPtr < Office::CommandBar> spCmdBar;

// 通過CommandBar擷取Outlook主菜單
hr = spCmdBars->get_ActiveMenuBar(&spCmdBar); 
if (FAILED(hr))
	return hr;

// 擷取菜單條的CommandBarControls 
spCmdCtrls = spCmdBar->GetControls(); 
ATLASSERT(spCmdCtrls);
		
// 在第5個"工具"菜單下新增一菜單條
CComVariant vItem(5);
spCmdCtrl= spCmdCtrls->GetItem(vItem);
ATLASSERT(spCmdCtrl);
		
IDispatchPtr spDisp;
spDisp = spCmdCtrl->GetControl(); 
		
// 擷取菜單條CommandBarPopup接口
CComQIPtr < Office::CommandBarPopup> ppCmdPopup(spDisp);  
ATLASSERT(ppCmdPopup);
		
spCmdBarCtrls = ppCmdPopup->GetControls();
ATLASSERT(spCmdBarCtrls);
		
CComVariant vMenuType(1); // 控件類型 - menu
CComVariant vMenuPos(6);  
CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vMenuShow(VARIANT_TRUE); // 菜單将顯示
CComVariant vMenuTemp(VARIANT_TRUE); // 臨時		
		
		
CComPtr < Office::CommandBarControl> spNewMenu;
// 用Add方法建立新的菜單條
spNewMenu = spCmdBarCtrls->Add(vMenuType, 
				vMenuEmpty,
				vMenuEmpty,
				vMenuEmpty,
				vMenuTemp); 
ATLASSERT(spNewMenu);
		
spNewMenu->PutCaption(bstrNewMenuText);
spNewMenu->PutEnabled(VARIANT_TRUE);
spNewMenu->PutVisible(VARIANT_TRUE); 
		
// 利用CommandBarButton來在菜單條前顯示位圖
CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(spNewMenu);
ATLASSERT(spCmdMenuButton);
spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);
		
// 同新增工具條第一個按鈕位圖相同方法
spCmdMenuButton->PasteFace(); 

// 顯示菜單	
spNewMenu->PutVisible(VARIANT_TRUE); 

m_spMenu = spCmdMenuButton;
      

這樣,通過在Outlook中通過上面提到的方法加載COM插件,就可以看到如圖一所示的界面效果了,但是點選時沒有響應,最後就讓我們來解決這個問題。

工具條按鈕CommandBarButton派發接口的響應事件是_CommandBarButtonEvents。ATL提供了二種模闆類IDispEventImpl<>和IDispEventSimpleImpl<>來實作接口事件的接收,這裡我們使用IDispEventSimpleImpl來實作(因為它不需要額外的類型庫資訊)。它需要設定SINK(接收)映射,通過_ATL_SINK_INFO結構來回調參數資訊,最終通過DispEventAdvise和DispEventUnadvise來與源接口連接配接或斷開。實作方法如下:

1. 在COutlookAddin繼承類中加入IDispEventSimpleImpl繼承,代碼如下:

class ATL_NO_VTABLE COutlookAddin : 
	public CComObjectRootEx<CComSingleThreadModel>,
	……
	public IDispEventSimpleImpl<1,COutlookAddin,&__uuidof(Office::_CommandBarButtonEvents)>
      
2. 聲明_ATL_SINK_INFO結構回調參數資訊。在OutlookAddin.h檔案中加入下面語句:
// 按鈕事件響應資訊聲明
extern _ATL_FUNC_INFO OnClickButtonInfo;
      
在OutlookAddin.cpp檔案中加入定義語句,如下:
// 按鈕事件響應資訊定義
_ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
      
3. 加入Sink映射,如下:
EGIN_SINK_MAP(COutlookAddin)
	SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo)
	SINK_ENTRY_INFO(2, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo)
	SINK_ENTRY_INFO(3, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickMenu, &OnClickButtonInfo)
END_SINK_MAP()
      
4. 加入事件函數。在OutlookAddin.h中加入聲明:
void __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);      
在OutlookAddin.cpp中加入實作:
// 工具條按鈕1點選事件響應函數
void __stdcall COutlookAddin::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,
						VARIANT_BOOL * CancelDefault)
{
	USES_CONVERSION;
	CComQIPtr<Office::_CommandBarButton> pCommandBarButton(Ctrl);
		
	HINSTANCE result=ShellExecute(NULL, _T("open"), _T("http://www.vckbase.com"), NULL,NULL, SW_SHOW);
}
      

5. 最後,打開或斷開與接口的連接配接。方法如下

● 在OnConnection接口函數的最後部分,加入下面代碼來打開連接配接:

CommandButton1Events::DispEventAdvise((IDispatch*)m_spButton);      
● 在OnDisconnection接口函數中,加入下面代碼來斷開連接配接:
CommandButton1Events::DispEventUnadvise((IDispatch*)m_spButton);      

到此就完成一個Office内部插件的最小需求了,大家可以編譯後打開Outlook2000看看效果如何,詳細代碼可參看文章所帶示例源碼,内有詳細注釋。

參考文獻:

Building an Office2K COM addin with VC++/ATL -- Amit Dey

ATL開發指南(第二版) – Tom Armstrong & Ron Patton

譯者聯系方式:

位址:陝西省西安市勞動路2号院六單元

郵編:710082

編者EMAIL:[email protected]

未來工作室(Future Studio)

繼續閱讀