程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> Office 2000下內部COM插件的編程實現

Office 2000下內部COM插件的編程實現

編輯:關於VC++

簡介

你也許曾在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的內部插件的全過程。效果圖如下所示:

打開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

本文配套源碼

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved