天天看點

Outlook Add-in

  利用VC++/ATL開發Office 2003 COM插件

最近,我為一個客戶寫了一個Outlook2003的COM插件。當我為這個工程寫代碼的時候,我遇到了很多用C++無法解決的問題。對于一個初學者來說,用ATL編寫插件是非常棘手的。網上大多數Office開發的例子都是VB/VBA相關的,幾乎沒有用ATL開發的。是以,我整理了一些知識,希望能夠對大家有所幫助。

在這篇文章裡的代碼并沒有進行優化,并且附帶的例子可能有一些記憶體洩露,也會有一些COM實作上的不足。但為了使讀者便于了解,我盡量使實作過程簡單化。為了寫這篇文章我花了很多時間,萬一還存在什麼錯誤,請給我發個郵件。

概況:

如果你是個COM/ATL的初學者,推薦你閱讀Amit Dey’s article for building an Outlook 2000 add-in

這篇文章将要講述下面的技術:

  • 基本的Outlook 2003 插件
  • 接收Explorer事件
  • 結合CDO和Outlook對象模型
  • 利用CDO檢視消息的安全性
  • 利用CDO為Outlook項目添加自定義區域
  • 以程式設計方式定制條目的分組和分類
  • 向右鍵菜單添加新項
  • 以程式設計的方式利用MSI加載CDO

Office COM插件必須實作IDTExtensibility2接口。IDTExtensibility2接口定義于MSADDIN Designer typelibrary (MSADDNDR.dll/MSADDNDR.tlb)檔案中,所有繼承于IDTExtensibility2接口的COM插件必須實作5個方法:

  • OnConnection
  • OnDisconnection
  • OnAddinUpdate
  • OnBeginShutDown 
  • OnStartupComplete

注冊:

所有Office插件都是注冊到以下系統資料庫條目的(Outlook指代應用程式名字)

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

除此之外,插件還可以通過其他系統資料庫條目來識别Outlook。

開始:

我們先來編寫一個最基本的COM插件。然後,我們從頭開始一步步地生成一個插件,這個插件将把簡單郵件和加密郵件分到不同的組中。

這篇文章假定一你是個VC++ COM程式員,并且也有一些基于ATL的元件開發和OLE/自動化方面的經驗,盡管這也不是必須的。插件是為Outlook 2003設計的,是以你機器上必須安裝帶有CDO的Office 2003。程式代碼使用VC++ 6.0 sp3+/ATL3.0建立,在安裝有Office 2003的WinXP Pro SP1上通過測試。

啟動VC++開發環境,建立一個工程,選擇ATL COM AppWizard,為工程命名為Outlook Addin,确定。選擇Dynamic Link Library,完成。

然後,點選菜單“插入”->“建立ATL對象”,選擇“Simple Object”,命名為OAddin,選擇Attributes标簽,選中Support ISupportErrorInfo,其他選項預設。

現在,我們可以來實作IDTExtensibility2接口了。在COAddin類上點右鍵,選擇Implement Interface,彈出Browse Type library向導對話框。選擇Microsoft Add-in Designer(1.0),OK。如果沒有列出來,要到檔案夾<drive>/Program Files/Common Files/Designer裡面去找。

向導實作了我們所選擇的接口,并為IDTExtensibility2接口的5個方法提供了預設實作。現在,一個基本的自動化COM對象就準備好了。通過向rgs檔案添加注冊條目,我們就可以用Outlook來注冊這個插件。打開檔案OAddin.rgs,在檔案末尾插入以下代碼:

HKCU

{

      Software

    {

        Microsoft

        {

            Office

            {

                Outlook

                {

                    Addins

                    {

                        'OAddin.OAddin'

                        {

                            val FriendlyName = s 'SMIME Addin'

                            val Description = s 'ATLCOM Outlook Addin'

                            val LoadBehavior = d '00000003'

                            val CommandLineSafe = d '00000000'

                        }

                    }

                }

            }

        }

    }

}

注冊條目看起來是很簡單的。LoadBehaviour表明了Outlook裝載COM插件的時機。我們的插件要在啟動時裝載,是以它的值設為3。現在,Build這個工程。然後在outlook裡,點選工具->選項,在其他頁點選進階選項->COM 附加元件,就可以看見我們的Addin了.

下一步,我們的任務是接收Explorer事件(Sink Explorer Events).

Sinking Explorer Events:

Outlook對象模型提供了Explorer對象,它封裝了Outlook的函數。這些Explorer對象可以用來進行Outlook程式設計。Explorer對象封裝了CommandBars、Panes、Veiws和Folders。在這個教程中,我們用ExplorerEvents接口接收Active Exploerer的FolderSwich事件。

在Outlook對象模型中,Application對象位于模型層次的最頂層,它表示整個應用程式。通過它的ActiveExplorer方法,我們可以得到代表Outlook目前視窗的Explorer對象。

當用于從一個檔案夾切換到另一個檔案夾時,可以觸發Explorer對象的一個事件。在我們的例子中,我們隻考慮郵箱檔案夾,不考慮聯系人、任務、月曆的FolderSwitch事件。在進行實際編碼之前,我們需要導入Office和Outlook類型庫。打開工程的stdafx.h檔案,加入以下#import指令:

#import "C:/Program Files/Microsoft Office/Office/mso9.dll" /

rename_namespace( "Office" ) named_guids using namespace Office;

#import "C:/Program Files/Microsoft Office/Office/MSOUTL9.olb"

rename_namespace( "Outlook" ), raw_interfaces_only, /

named_guids using namespace Outlook;

這些路徑需要根據你安裝的作業系統和Office目錄做出改變。

編譯工程導入類型庫。

現在,我們需要通過連接配接點實作由事件源喚起的事件接收接口。

ATL為ATL COM對象提供了IDispEventImpl<>和IDispEventSimpleImpl<>。我用IDispEventSimpleImpl<>建立了一個接收器映射(sink map)。我們要從IDispEventSimpleImpl<>派生我們的COAddin類并用_ATL_SINK_INFO結建構立params。然後建立接收器接口并用接口源分别調用DispEventAdvise和DispEventUnAdvise來初始化和終止連接配接。

代碼看起來是這樣的:

從IDispEventSimpleImpl派生你自己的類:

//

class ATL_NO_VTABLE COAddin :

public CComObjectRootEx<CComSingleThreadModel>,

...

...

 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents)>

_ATL_SINK_INFO結構用來描述回調參數。打開add-in對象的頭檔案OAdin.h,在最頂部加入下面一行代碼:

extern _ATL_FUNC_INFO OnSimpleEventInfo;

然後打開cpp檔案,在上部加入以下代碼:

_ATL_FUNC_INFO OnSimpleEventInfo ={CC_STDCALL,VT_EMPTY,0};

建立一個回調函數:

void __stdcall OnFolderChange();  (OAddin.h檔案中,譯者注)

void __stdcall COAddin::OnFolderChange()   (OAddin.cpp檔案中,譯者注)

{

  MessageBoxW(NULL,L"Hello folder Change Event",L"Outlook Addin",MB_OK);

}

現在,為了利用接收器映射,我們将要用到ATL BEGIN_SINK_MAP() 和END_SINK_MAP()。每個條目都由SINK_ENTRY_INFO來描述。dispid代表事件的DISPID。你可以在類型庫中找到這些ID,也可以用Outlook Spy來檢視它們。

BEGIN_SINK_MAP(COAddin)

  // sink the event for Explorer

  // the first parameter is the same as given above while driving the class

  // the third parameter is the event identifier to sink i.e FolderChange

  // rest of event id's can also be located using OutlookSpy or type libraries

  SINK_ENTRY_INFO(1,__uuidof(Outlook::ExplorerEvents),0xf002

       ,OnFolderChange,&OnSimpleEventInfo)

  END_SINK_MAP()

(以上代碼位于OAdin.h檔案中,譯者注)

現在,來到OnConnection函數(我們用向導實作接口的時候生成的),修改代碼如下:

//

STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode,

 IDispatch * AddInInst, SAFEARRAY * * custom)

{

    CComQIPtr <Outlook::_Application> spApp(Application);

    ATLASSERT(spApp);

    m_spApp = spApp; //store the application object

    CComPtr<Outlook::_Explorer> spExplorer;

    spApp->ActiveExplorer(&spExplorer);

    m_spExplorer = spExplorer; // store the explorer object

    HRESULT hr = NULL;

    //Sink Explorer Events to be notified for FolderChange Event

    DispEventAdvise((IDispatch*)spExplorer);

    hr = ExpEvents::DispEventAdvise((IDispatch*)m_spExplorer,

            &__uuidof(Outlook::ExplorerEvents));

}

到此為止,我們建立了Explorer對象的FolderSwitch事件映射。編譯這個工程。如果一切OK,當你在Outlook中從收件箱向發件箱或其他檔案夾切換時,對話框就會彈出來。如果你切換到其他的檔案夾,然後折疊郵件區域,同樣會接受到這個對話框。稍後我們将用程式邏輯來控制它。

結合CDO和Outlook對象模型

CDO(Collaboration Data Objects,協作資料對象)是一項建立消息通知或協作應用程式的技術。CDO可以單獨地使用,也可以用在Outlook對象模型的連接配接中來擷取更多的對Outlook的通路途徑。

我們的例子處理“自定義郵件分組”,按照郵件是SMIME(加密的),還是簡單郵件(Simple emails)。Outlook對象模型對标記的加密郵件不提供任何互動接口。事實上,如果你試圖研究代表一封加密郵件的MailItem對象,你會發現大約80%的屬性和方法是無法通路的。現在,我們将用CDO來實作消息安全的可用性。

為了定制郵件分組,唯一的辦法就是向MailItem增加自定義屬性字段X,然後讓Outlook按照屬性X對郵件進行分組。然而不幸的是,一封标記過的加密郵件的字段屬性是無效的,并且我們無法利用Outlook對象模型增加任何字段。

另一方面,CDO不是Outlook對象模型的一部分,它不提供任何基于某一功能的事件,我們也不能利用CDO操作Outlook對象。是以,為了用CDO通路目前選中的檔案夾,我們需要先用Outlook對象模型得到目前選中的檔案夾,然後得到它的唯一辨別符,傳給CDO,使CDO傳回檔案夾。

準備CDO編碼

首先,在Office XP和随後版本中,預設安裝是沒有安裝CDO的。是以你必須首先确定你的客戶安裝了CDO。此外,這個教程也要求你在機器上安裝CDO。CDO不能從應用程式直接配置,必須用Microsoft Office MSI從OfficeCD光牒安裝。

教程的結尾有一個例子,展示了如何程式設計調用MSI自動安裝CDO。

我們将要用到下面CDO對象:Session, Messages, Message, Folder, Fields 和Field。為使這些對象在應用程式中可用,必須導入CDO。打開stdafx.h,加入下面代碼:

#import "F://Program Files//Common Files//System//MSMAPI//1033//cdo.dll" /

rename_namespace("CDO")

我們可以通過Active Explorer的GetCurrentFolder用Outlook對象模型來通路Outlook的目前檔案夾。在這裡,如果傳回的檔案夾的DefaultItemType屬性不是olMailItem,我們可以終止程式的執行。傳回檔案夾的EntryID屬性把它和其他檔案夾區分開來,我們将向CDO傳遞這個屬性以得到目前檔案夾。

首先,轉到OnFolderChange添加下面代碼:

//

void __stdcall COAddin::OnFolderChange()

{

   if(!m_bEnableSorting) // its a boolean variable to identify

    //weither to sort or not

    return;

   CDO::_SessionPtr Session("MAPI.Session");

   //logon to CDO

   // the first parameter is the Profile name you want to use.

   // the rest of two false tell CDO not to display

   // any user interface if this profile is not found.

   Session->Logon("Outlook","",VARIANT_FALSE,VARIANT_FALSE);

   //its the OutlookObject Model MAPIFolder object. it is used to findout

   //currently selected folder of outlook as CDO doesn't

   // provide any direct interface

   //to get the currently selected folder of outlook

   CComPtr<Outlook::MAPIFolder> MFolder;

   m_spExplorer->get_CurrentFolder(&MFolder);

   //this example only deals with outlook Mail Items

   OlItemType o;

   MFolder->get_DefaultItemType(&o);

   if(o != olMailItem)

    return;

   BSTR entryID;

   MFolder->get_EntryID(&entryID);

   CDO::MessagesPtr pcdoMessages;

   // get the selected folder in CDO using the EntryID of Outlook::MapiFolder

   CDO::FolderPtr  pFolder= Session->GetFolder(entryID);

   if(pFolder) //making sure

   {

     //play with the folder messages here

   }

}

OK,到此為止,我們得到了CDO Folder,然後就可以擷取Messages集合了。

利用CDO檢視一個消息是否具有安全性

現在,讓我們來看看如何判斷一封郵件是“encrypted”還是“signed”。下面的KB表明了全部原理:"KB 194623",但是我發現對我的客戶來說它并不正常,因為他們有很多郵件用戶端,不是每個用戶端都和這個KB描述的一緻。事實上,它也說明了,“使用這些屬性程式設計決定一條消息是否具有安全性是不可靠的”。為了達到結果,我所能夠找到的唯一的辦法是,每封具有安全性的郵件都有一個特殊的附件。這個附件包含了Outlook “encrypted/signed”的内容。這個附件的擴充名是“p7m”,MIME類型是application/x-pkcs7-mime。在我們對解決方案中,我們的方法是:

1. 得到檔案夾的Messages集合。

2. 枚舉集合得到Message。

3. 枚舉Attachments。

4. 得到每個Attachments的Fields。

5. 枚舉Fields,找到Field(H370E001E)(這是Attachment的MIME類型)。

6. 用"application/x-pkcs7-mime"測試Field值。

現在,為你的類添加一個新的成員函數IsCDOEncrypted。這個函數接收一個單獨的CDO Message對象,傳回一個布爾類型的值訓示這個message的狀态。下面就是上述理論的代碼片斷:

//

BOOL COAddin::IsCDOEncrypteD(CDO::MessagePtr pMessageRemote)

{

BOOL bEncrypted = false;

CDO::MessagePtr pMessage;

pMessage = pMessageRemote;

//get the attachments of the CDO message

CDO::AttachmentsPtr pAttachments;

pAttachments = pMessage->Attachments;

_variant_t nCount =pAttachments->Count;

long nTotal = nCount.operator long();

//enumerate the attachments

for(int i = 0; i < nTotal; i++)

{

 // get the attachment from the

    //attachments collection

    CDO::AttachmentPtr pAttachment;

    CComVariant nItem(i+1);//1 based index

    pAttachment = pAttachments->Item[nItem];

    //get the Fields collection of the Attachment object

    CDO::FieldsPtr pFields;

    pFields = pAttachment->Fields;

    _variant_t nVFields = pFields->Count;

    for(int z = 0; z < nVFields.operator long() ; z++)

    {

        // get the field from fields collection.

        CComVariant nFieldItem(z+1);

        CDO::FieldPtr pField;

        pField = pFields->Item[nFieldItem]; //1 based index

        //check if this field is what we need

        //the mime type of the

        //attachment is stored as Field in the CDO message

        //the field that contains the mime type of the CDO Message

        // has an ID of 923664414 (more such ID's can be

        //found in CDO in HTTP transport Header section)

        BSTR bstrFieldID;

        bstrFieldID =  pField->GetID().operator _bstr_t();

        if(wcscmp(bstrFieldID,L"923664414")==0)

            // get the mime type of the attachment

        {

            // check the mime type of the mail item now.

            // compare the field value.

            if(wcscmp(pField->Value.operator _bstr_t(),

                L"application/x-pkcs7-mime")==0)

            {

                bEncrypted = true;

                break;

            }

        }

    }

}

pAttachments->Release();

pAttachments = NULL;

pMessage->Update(); //its not a necessary call.

return bEncrypted;

}

利用CDO向Outlook添加自定義字段

Outlook對象模型暴露了Fields屬性,可以增加自定義的Fields屬性。我們将通過CDO向郵件增加自定義Fields。

在OnFolderChange函數中,周遊messages時,可以使用IsCDOEncrypted函數。回到OnFolderChange函數。

下面的代碼片斷為每封郵件增加了自定義fields。

 //

   .....

   // previous code of OnFolderChange

   .....

   CDO::FolderPtr pFolder= Session->GetFolder(entryID);

   if(pFolder) //making sure

   {

     //get the message of the Folder

     pcdoMessages = pFolder->Messages;

     CDO::MessagePtr pMessage = pcdoMessages->GetFirst();

     while(pMessage) // iterate them

     {

      //check if the message is encrytped

      BOOL bEncrypted = IsCDOEncrypteD(pMessage);

      if(bEncrypted)

      {

        //add a custom field to the outlook message

        //an encrypted email

        CDO::FieldsPtr pMessageFields = pMessage->Fields;

        //Add a custom field

        //Encrypted of type String(8)

        //and set its value to "Encrypted"

        pMessageFields->Add(L"Encrypted",

                CComVariant(8),L"SMIME Emails");

        pMessage->Update();

      }

      else

      {

        CDO::FieldsPtr pMessageFields = pMessage->Fields;

        //Add a custom field

        pMessageFields->Add(L"Encrypted",CComVariant(8),L"Simple Emails");

          // you must call Update message to reflect the new field to

          // mail item

        pMessage->Update();

      }

      pMessage = pcdoMessages->GetNext();

     }

   }

定制分組和分類

Outlook現在暴露了新的基于XML的視圖系統。你可以用XML建立你自己的視圖,也可以改變XML來修改現有的視圖。下面是收件箱的标準XML:

//

<?xml version="1.0"?>

<view type="table">

 <viewname>Messages</viewname>

 <viewstyle>table-layout:fixed;width:100%;font-family:Tahoma;

font-style:normal;font-weight:normal;font-size:8pt;

color:Black;font-charset:0</viewstyle>

 <viewtime>0</viewtime>

 <linecolor>8421504</linecolor>

 <linestyle>3</linestyle>

 <usequickflags>1</usequickflags>

 <collapsestate></collapsestate>

 <rowstyle>background-color:#FFFFFF</rowstyle>

 <headerstyle>background-color:#D3D3D3</headerstyle>

 <previewstyle>color:Blue</previewstyle>

 <arrangement>

  <autogroup>1</autogroup>

  <collapseclient></collapseclient>

 </arrangement>

 <column>

  <name>HREF</name>

  <prop>DAV:href</prop>

  <checkbox>1</checkbox>

 </column>

 <column>

  <heading>Importance</heading>

  <prop>urn:schemas:httpmail:importance</prop>

  <type>i4</type>

  <bitmap>1</bitmap>

  <width>10</width>

  <style>padding-left:3px;;text-align:center</style>

 </column>

 <column>

  <heading>Icon</heading>

  <prop>http://schemas.microsoft.com/mapi/proptag/0x0fff0102</prop>

  <bitmap>1</bitmap>

  <width>18</width>

  <style>padding-left:3px;;text-align:center</style>

 </column>

 <column>

  <heading>Flag Status</heading>

  <prop>http://schemas.microsoft.com/mapi/proptag/0x10900003</prop>

  <type>i4</type>

  <bitmap>1</bitmap>

  <width>18</width>

  <style>padding-left:3px;;text-align:center</style>

 </column>

 <column>

  <format>boolicon</format>

  <heading>Attachment</heading>

  <prop>urn:schemas:httpmail:hasattachment</prop>

  <type>boolean</type>

  <bitmap>1</bitmap>

  <width>10</width>

  <style>padding-left:3px;;text-align:center</style>

  <displayformat>3</displayformat>

 </column>

 <column>

  <heading>From</heading>

  <prop>urn:schemas:httpmail:fromname</prop>

  <type>string</type>

  <width>49</width>

  <style>padding-left:3px;;text-align:left</style>

  <displayformat>1</displayformat>

 </column>

 <column>

  <heading>Subject</heading>

  <prop>urn:schemas:httpmail:subject</prop>

  <type>string</type>

  <width>236</width>

  <style>padding-left:3px;;text-align:left</style>

 </column>

 <column>

  <heading>Received</heading>

  <prop>urn:schemas:httpmail:datereceived</prop>

  <type>datetime</type>

  <width>59</width>

  <style>padding-left:3px;;text-align:left</style>

  <format>M/d/yyyy||h:mm tt</format>

  <displayformat>2</displayformat>

 </column>

 <column>

  <heading>Size</heading>

  <prop>http://schemas.microsoft.com/mapi/id

/{00020328-0000-0000-C000-000000000046}/8ff00003</prop>

  <type>i4</type>

  <width>30</width>

  <style>padding-left:3px;;text-align:left</style>

  <displayformat>3</displayformat>

 </column>

 <groupby>

  <order>

   <heading>Received</heading>

   <prop>urn:schemas:httpmail:datereceived</prop>

   <type>datetime</type>

   <sort>desc</sort>

  </order>

 </groupby>

 <orderby>

  <order>

   <heading>Received</heading>

   <prop>urn:schemas:httpmail:datereceived</prop>

   <type>datetime</type>

   <sort>desc</sort>

  </order>

 </orderby>

 <groupbydefault>2</groupbydefault>

 <previewpane>

  <visible>1</visible>

  <markasread>0</markasread>

 </previewpane>

</view>

在這個教程中,我們關注的是<groupby> 和 <orderby>兩個節點。這裡我隻是自定義分組功能,你可以重用相同的代碼來自定義分類功能。

為了定制分組功能,你可以在Outlook的"Customize Current View"選項中設定"User Defined fields"。為了以程式設計方式實作,由于我們已經增加了自定義字段,我們僅需要像下面意義修改<groupby>元素:

//

 <groupby>

  <order>

   <heading>Encrytped</heading>

   <prop>http://schemas.microsoft.com/mapi/string/

    {00020329-0000-0000-C000-000000000046}/Encrypted</prop>

   <type>string</type>

   <sort>asc</sort>

  </order>

 </groupby>

OK,新增一個成員函數ChangeView(Outlook::ViewPtr pView)。這個函數接收一個Outlook View,傳回它的XML,并相應地作出修改。Outlook的MAPIFolder對象的CurrentView屬性傳回目前視圖。Outlook::View的XML屬性傳回目前視圖的XML。這裡,我使用MSXML parser修改XML。你也可以使用任何友善的方法。代碼如下:

//

void COAddin::ChangeView(Outlook::ViewPtr pView)

{

       HRESULT hr;

      IXMLDOMDocument2 * pXMLDoc;

      IXMLDOMNode * pXDN;

     //...create an instance of IXMLDOMDocument2

      hr = CoInitialize(NULL);

      hr = CoCreateInstance(CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER,

      IID_IXMLDOMDocument2, (void**)&pXMLDoc);

      hr = pXMLDoc->QueryInterface(IID_IXMLDOMNode, (void **)&pXDN);

      //get the view's XML

      BSTR XML;

      pView->get_XML(&XML);

     //loaod the XML

      VARIANT_BOOL bSuccess=false;

      pXMLDoc->loadXML(XML,&bSuccess);

      CComPtr<IXMLDOMNodeList> pNodes;

      //check groupby element exists

      pXMLDoc->getElementsByTagName(L"groupby",&pNodes);

      long length = 0;

      pNodes->get_length(&length);

      if(length> 0)

      {

            // groupby element already exists.

            // get the first occourance of groupby element

          HRESULT hr = pNodes->get_item(0,&pXDN);

          IXMLDOMNode *pXDNTemp,*pXDNTemp2;

          pXDN->get_firstChild(&pXDNTemp);

          pXDNTemp->get_firstChild(&pXDNTemp2);

          _variant_t vtHeading("Encrypted"),vtType("string"),

          vtProp("http://schemas.microsoft.com/mapi/string/ /

            {00020329-0000-0000-C000-000000000046}/Encrypted");

          // get the heading element

          //the first element is the name of the field.

          pXDNTemp2->put_nodeTypedValue(vtHeading);

          // get the prop element

          pXDNTemp2->get_nextSibling(&pXDNTemp2);

          pXDNTemp2->put_nodeTypedValue(vtProp);

          pXDNTemp2->get_nextSibling(&pXDNTemp2);

          // get the type elment. it tell what sort of sorting goin to be

          pXDNTemp2->put_nodeTypedValue(vtType);

      }else

      {

          //groupby element doesn't exists

          IXMLDOMElement *pGroupByElement;

          //create the element

          pXMLDoc->createElement(L"groupby",&pGroupByElement);

          IXMLDOMElement *pOrderElement;

          IXMLDOMNode *pOrderNode;

          //create the Order element in side groupby element

          pXMLDoc->createElement(L"order",&pOrderElement);

          pGroupByElement->appendChild(pOrderElement,&pOrderNode);

          IXMLDOMElement *pHeadingElement,*pPropElement,*pTypeElement,

                                                        *pSortElement;

          IXMLDOMNode *pHeadingNode,*pPropNode,*pTypeNode, *pSortNode;

           _variant_t vtHeading("Encrypted"),vtSort("asc"),vtType("string"),

          vtProp("http://schemas.microsoft.com/mapi/string//

          {00020329-0000-0000-C000-000000000046}/Encrypted");

          //create the heading element and populate it with value

          pXMLDoc->createElement(L"heading",&pHeadingElement);

          pOrderNode->appendChild(pHeadingElement,&pHeadingNode);

          pHeadingNode->put_nodeTypedValue(vtHeading);

          //create the prop element and populate it with value

          pXMLDoc->createElement(L"prop",&pPropElement);

          pOrderNode->appendChild(pPropElement,&pPropNode);

          pPropNode->put_nodeTypedValue(vtProp);

          //create the type element and populate it with value

          pXMLDoc->createElement(L"type",&pTypeElement); 

          pOrderNode->appendChild(pTypeElement,&pTypeNode);

          pTypeNode->put_nodeTypedValue(vtType);

          pXMLDoc->createElement(L"sort",&pSortElement);

          pOrderNode->appendChild(pSortElement,&pSortNode);

          pSortNode->put_nodeTypedValue(vtSort);

          HRESULT hr;//= pXMLDoc->insertBefore(pOrderNode,NULL,NULL);

          IXMLDOMElement *pXMLRootElement;

          if(!FAILED(pXMLDoc->get_documentElement(&pXMLRootElement)))

          {

               _variant_t _vt;

               hr= pXMLRootElement->insertBefore(pGroupByElement,_vt,NULL);

          }

     }

      // get the xml out of the MSXML document object

      pXMLDoc->get_xml(&XML);

      // put the xml to View

      pView->put_XML(XML);

      // Save method is a must to reflect change to the view

      pView->Save();

}

下面添加在OnFolderChange函數中的代碼實作了分組功能:

// OnFolderChange

// ....

// .. Old Code goes here

  pMessageFields->Add(L"Encrypted",CComVariant(8),L"Simple Emails");

         // you must call Update message to reflect the new field to

          // mail item

        pMessage->Update();

   }

   pMessage = pcdoMessages->GetNext();

   }  // end of while

  CComPtr<Outlook::View> pV;

  HRESULT hr = MFolder->get_CurrentView(&pV);

  //now change its view state.

  ChangeView(pV);

}

喔,打字打得好累 :O

好了,現在可以編譯了,如果一切順利,你的郵件将會被分成兩個組。

向右鍵菜單添加選項

我想這是才是本教程最值得大家期待的部分。比起鑽研密碼邏輯來說,大部分人更關心這個。

Amit Dey就“向菜單和工具欄添加新項”做了很多解釋。如果你沒有讀過他的文章,先去讀一下,因為我将不再詳細解釋CommandBars這類東西。

為了向Outlook右鍵菜單增加新項,我們需要映射Command Bars的OnUpdate事件。我們可以使Command Bars對象通過Explorer的CommandBars屬性接收OnUpdate事件。

首先,增加私有成員變量以存儲CommandBars對象。打開頭檔案OAddin.h,添加下面代碼:

CComPtr<Office::_CommandBars> m_spCommandbars; //commandbars

CComPtr<Office::CommandBarControl> m_pSortButton; // Sort Button

為了接收OnUpdate事件,COAddin類必須做出一些修改。打開OAddin.h檔案,從CommandBarsEvents繼承你的類:

//

class ATL_NO_VTABLE COAddin :

public CComObjectRootEx<CComSingleThreadModel>,

...

...

 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents )>,

public IDispEventSimpleImpl<2,COAddin,&__uuidof(Office::_CommandBarsEvents)>

然後在OAddin.h中聲明一個回調函數:

void __stdcall OnContextMenuUpdate();

打開cpp檔案,增加這個函數的定義:

//

void __stdcall COAddin::OnContextMenuUpdate()

{

    MessageBoxW(NULL,L"Hello Menu Update Event",L"Outlook Addin",MB_OK);

}

建立接收器映射:

//

  BEGIN_SINK_MAP(COAddin)

  // sink the event for Explorer

  // the first parameter is the same as given above while driving the class

  // the third parameter is the event identifier to sink i.e FolderChange

  // rest of event id's can also be located using OutlookSpy or type libraries

  SINK_ENTRY_INFO(1,__uuidof(Outlook::ExplorerEvents),0xf002

       ,OnFolderChange,&OnSimpleEventInfo)

  SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarsEvents),0x1,

       OnContextMenuUpdate,&OnSimpleEventInfo)

  END_SINK_MAP()

現在,從源接口接收事件接口的通道已經打通。接收事件的最佳地方是OnConnection,回到OnConnection函數,增加以下代碼。如上所述,我們可以從Explorer對象得到CommandBars。

//... OnConnection function

// .. earlier code goes here

 hr = ExpEvents::DispEventAdvise((IDispatch*)m_spExplorer,

                         &__uuidof(Outlook::ExplorerEvents ));

// .....

//.....

  CComPtr < Office::_CommandBars> spCmdBars;

  hr = spExplorer->get_CommandBars(&spCmdBars);

  if(FAILED(hr))

   return hr;

  m_spCommandbars = spCmdBars;

    //Sink the OnUpdate event of command bars

  hr = CmdBarsEvents::DispEventAdvise((IDispatch*)m_spCommandbars,

            &__uuidof(Office::_CommandBarsEvents));

OK!當一個CommandBar需要更新時,OnUpdate事件就激發了。是以我們現在需要的是找到右鍵菜單,往裡面添加新項。右鍵菜單具有一個固定的名字:Context Menu。我們可以枚舉CommandBars來找到Context Menu。

CommandBars對象包含了子控件CommandBar。每個CommandBar又可以包含CommandBarControl 和CommandBarButtons。我們要向菜單增加一個CommandBarControl。我們可以用CommandBar的Add方法向CommandBar增加一個新項。是以我們的任務是:

 1.枚舉CommandBars以找到名字為Context Menu的CommandBar。

 2.得到CommandBar控件。

 3.調用這個控件的Add方法向它插入新項。

好了,現在最重要的一件事是,CommandBar對象是被Outlook鎖定的。為了調用它的Add方法,必須取消對CommandBarControl對象的保護,否則對Add的通路将會失敗。我們可以用CommandBar的Protection屬性來取消這個保護。

下面是修改OnContextMenuUpdate的代碼:

//

void __stdcall COAddin::OnContextMenuUpdate()

{

  CComPtr<Office::CommandBar> pCmdBar;

  BOOL bFound =false;

  for(long i = 0; i < m_spCommandbars->Count ; i++)

  {

   CComPtr<Office::CommandBar> pTempBar;

   CComVariant vItem(i+1);   //zero based index

   m_spCommandbars->get_Item(vItem,&pTempBar);

   if((pTempBar) && (!wcscmp(L"Context Menu",pTempBar->Name)))

   {

    pCmdBar = pTempBar;

    bFound = true;

    break;

   }

  // pCmdBar->Release();

  }

  if(!bFound)

     return;

  if(pCmdBar)//make sure a valid CommandBar is found

  {

     soBarProtection oldProtectionLevel = pCmdBar->Protection ;

    // change the commandbar protection to zero

    pCmdBar->Protection = msoBarNoProtection;

    //set the new item type to ControlButton;

    CComVariant vtType(msoControlButton);

    //add a new item to command bar

    m_pSortButton = pCmdBar->Controls->Add(vtType);

    //set a unique Tag that u can be used to find your control in commandbar

    m_pSortButton ->Tag = L"SORT_ITEM"; 

    //a caption

    m_pSortButton ->Caption = L"Sort By SMIME";    

    // priority (makes sure outlook includes this item in every time)

    m_pSortButton ->Priority =1 ;

    // visible the item

    m_pSortButton ->Visible = true;

  }

}

到此為止,編譯這個工程,Outlook滑鼠右鍵菜單就會出現一個新項。但是在向它添加一個句柄之前,它是無效的。是以,為了接收事件,讓我們回到OAddin類的頭檔案,使這個類從_CommandBarButtonEvents繼承。

//

class ATL_NO_VTABLE COAddin :

public CComObjectRootEx<CComSingleThreadModel>,

...

...

 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents )>,

public IDispEventSimpleImpl<2,COAddin,&__uuidof(Office::_CommandBarsEvents)>

,

 // Its possible to sink event for a single command bar button

 // and you can recognize the control using its face text

 // but for this example i've sinked event for each command bar button

 public IDispEventSimpleImpl<3,COAddin,

     &__uuidof(Office::_CommandBarButtonEvents)>,

然後再次用ATL_SINK_INFO結構描述回調參數。打開add-in對象的頭檔案OAddin.h,在最頂部加入下面一行代碼:

extern _ATL_FUNC_INFO OnClickButtonInfo;

打開這個類的cpp檔案,在頂部加入下面代碼:

_ATL_FUNC_INFO OnClickButtonInfo =

  {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};

建立回調函數:

//

void __stdcall OnClickButtonSort(IDispatch* Ctrl,VARIANT_BOOL * CancelDefault);

void __stdcall COAddin::OnClickButtonSort(IDispatch* Ctrl,VARIANT_BOOL * CancelDefault)

{

  // m_bEnableSorting is a member boolean variable

  if(!m_bEnableSorting)

  {

   m_bEnableSorting =true;

   OnFolderChange(); // Sort The elements of current view;

  }

  else

  {

   m_bEnableSorting = false;

  }

}

現在可以接收事件了。當新的CommandBarControl生成時接收事件:

// OnUpdate

// .... Old code goes here.

   m_pSortButton ->Visible = true;

  hr = CommandButtonEvents::DispEventAdvise((IDispatch*)m_pSortButton);

  if(hr != S_OK)

  {

   MessageBoxW(NULL,L"Menu Event Sinking failed",L"Error",MB_OK);   

  }

  CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(m_pSortButton);

好了,編譯工程,看看菜單項是怎麼工作的。點選一次,郵件會自動分類,再次點選,郵件就不再分類了。

如何改變菜單狀态使它為選中狀态呢?

喔,還有很多要寫的 :S,并且還有很多要讀的。

早些時候當我為一個客戶開發解決方案時,一個Microsoft MVP(我指的不是你;))對我說,無論是向右鍵菜單裡添加新項,還是改變菜單項為選中狀态,都是不可能的,我必須放棄這些功能把工程傳遞客戶。後來,我用Outlook Spy并參照Amit Dey的文章搞定了,雖然有些棘手,但并不是不可能的。

好了,為了改變菜單項為選中狀态,你隻需要為Office::msoButtonDown改變新加入CommandBarControl的State的屬性。對新插入的CommandBarControl,State是不可用的,是以必須把它轉換為CommandButton。

下面是OnUpdate的代碼:

// OnUpdate

// .... Old code goes here.

  m_pSortButton ->Visible = true;

 // ok this example needs to modify the new menu item to be displayed as CHECKED

  // as well we get the equivalent commandbarbutton object of this object.

  CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(m_pSortButton);

  if(m_bEnableSorting)

  {

     //if sorting is enabled check mark the new menu item

    ATLASSERT(spCmdMenuButton);

    spCmdMenuButton->State = Office::msoButtonDown;

    m_bEnableSorting = true;

   }

// .. rest of code goes here

  hr = CommandButtonEvents::DispEventAdvise((IDispatch*)m_pSortButton);

  if(hr != S_OK)

  {

   MessageBoxW(NULL,L"Menu Event Sinking failed",L"Error",MB_OK);   

  }

 注意:為了向菜單項添加圖示,你可以用方法向菜單項添加一個圖像。下面的代碼完成了這個工作:

//

HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),

   MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

 // put bitmap into Clipboard

  ::OpenClipboard(NULL);

  ::EmptyClipboard();

  ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);

  ::CloseClipboard();

  ::DeleteObject(hBmp);

 // change the button layout and paste the face

  spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);

  HRESULT hr = spCmdMenuButton->PasteFace();

  if (FAILED(hr))

   return ;

編譯這個工程,一個完整的插件就完成了。它可以按照安全性将郵件分類。

如何程式設計利用MSI安裝CDO

可以在程式中利用MSI安裝CDO。為了在C++工程中使用MSI,必須向工程導入MSI.dll:

#import "msi.dll" rename_namespace("MSI")

為了安裝CDO,MSI需要功能名字和産品代碼。産品代碼可以從傳給OnConnection函數的Outlook的Applicaton對象得到。下面是MSI的代碼:

//

BSTR bstrCode;

spApp->get_ProductCode(&bstrCode);  

MSI::InstallerPtr pInstaller(__uuidof(MSI::Installer));

MSI::MsiInstallState o = pInstaller->GetFeatureState(bstrCode,L"OutlookCDO");

if(o != MSI::msiInstallStateLocal)

{

 pInstaller->ConfigureFeature(bstrCode,L"OutlookCDO",MSI::msiInstallStateLocal);

}

OK,搞定!

在這個教程中,我盡力給出了最多的解釋。

提供的例子是用C++寫的,我曾經用VB實作。結果并不是最優的,而且你可能會發現一些COM實作上的不足。歡迎批評指正。謝謝!

繼續閱讀