|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionRecently, I wrote an Outlook2003 COM add-in for one of my clients. While coding the project, I’ve had a lot of issues to which I couldn’t find any solution for C++ especially. I found it quite tricky to code an add-in in ATL for beginners, and so to help others I thought to put down some information, since most of the Office related examples I found on the Internet were VB/VBA related and almost none with ATL. The code in this article is not really optimistic, and the sample attached with this article may have some memory leaks or some poor COM implementation, but the general approach has been kept simple for the reader to follow. Though I took quite sometime to write this, in case of any errors or bugs, kindly drop me a mail. OverviewIf you are a beginner in COM/ATL, I’ll suggest you read Amit Dey’s article for building an Outlook 2000 add-in. My article will describe the following techniques:
An Office COM add-in has to implement the
RegisteringAll of Office add-ins are registerted to the following registry entry, where Outlook is replaced with the application name. HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\Addins\<ProgID> There are some other entries through which add-ins are identified to Outlook. Getting StartedLet's start by writing a basic functional COM add-in. Then, we’ll move ahead to each step and will create an add-in that will group simple and encrypted emails to different groups. This article assumes that you are a VC++ COM programmer, and have had some experience with ATL based component development and OLE/Automation, although this is not strictly necessary. The add-in is designed for Outlook 2003, so you must have Office 2003 with CDO installed on your machine. The project code has been built with VC++ 6.0 SP3+/ATL3.0, and tested on WinXP Pro SP1 with Office 2003 installed. Fire up your VC IDE. Create a new project and chose ATL COM AppWizard as your project type, give it a name Outlook Addin, click OK. Choose Dynamic Link Library as your server type, and click Finish. Now, Add a New ATL Object using the Insert menu. Select Simple Object and give it a name OAddin, move to Attributes and check the Support ISupportErrorInfo checkbox, and choose the rest of the options as default. Now, it's time to actually implement the This wizard implements the selected interface and adds a default implementation for each of the five methods of 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'
}
}
}
}
}
}
}
The registry entries look pretty simple The So, the next step is to sink the Explorer events. Sinking Explorer EventsOutlook Object Model presents Explorer objects that wrap the functionality of Outlook. These Explorer objects can be used to program Outlook. Explorer objects wrap the CommandBars, Panes, Views, and Folders. For this tutorial, we'll interact with In the Outlook Object Model, the Explorer objects expose an event which is triggered whenever user switches from one folder to other. For this example, we'll only be concerned about folders for Mailbox, and will skip the Contacts, Tasks and Calendars #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; You may need to change these paths according to your OS and Office installation. Compile the project to import the type libraries. Now, we'll have to implement the event sink interface that will be called by the source event through connection points. ATL provides This is how the code looks: Derive your class from // 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 structure is used to describe the callback parameters. Open the header file of the add-in object (OAddin.h) and add the following line at the very top:
extern _ATL_FUNC_INFO OnSimpleEventInfo;
Open the cpp file of the class and add the following line to the top: _ATL_FUNC_INFO OnSimpleEventInfo ={CC_STDCALL,VT_EMPTY,0};
Create a callback function: void __stdcall OnFolderChange(); void __stdcall COAddin::OnFolderChange() { MessageBoxW(NULL,L"Hello folder Change Event",L"Outlook Addin",MB_OK); } Now, to setup the sink map, we'll use // 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),/*dispinterface*/0xf002 ,OnFolderChange,&OnSimpleEventInfo) END_SINK_MAP() Now, move to your // STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom) { omQIPtr <Outlook::_Application> spApp(Application); TLASSERT(spApp); _spApp = spApp; //store the application object ComPtr<Outlook::_Explorer> spExplorer; pApp->ActiveExplorer(&spExplorer); _spExplorer = spExplorer; // store the explorer object RESULT hr = NULL /Sink Explorer Events to be notified for FolderChange Event /DispEventAdvise((IDispatch*)spExplorer); r = ExpEvents::DispEventAdvise((IDispatch*)m_spExplorer, &__uuidof(Outlook::ExplorerEvents)); } Till here, we've mapped the Mixing CDO and Outlook Object ModelCDO (Collaboration Data Objects) is a technology for building messaging or collaboration applications. CDO can be used separately or in connection with Outlook Object Model to gain more access over Outlook. This example deals with the "Custom Grouping of emails", depending on their nature SMIME (encrypted) or Simple emails. Outlook Object Model doesn't provide any interface to interact with signed and encrypted emails. In fact, if you look into In order to customize the grouping of emails, the only way is to add custom field 'X' to the On the other hand, CDO is not a part of Outlook Object Model and it doesn't provide any event based functionality nor can you manipulate Outlook objects using CDO. So, to access currently selected folder in CDO, you have to use Outlook Object Model to retrieve the currently selected folder, get its unique identifier, and pass it to CDO to return the folder. Preparing your code to Use CDOThe very first statement about CDO is "CDO is not installed during the default installation of Office XP and later", and so you've to make sure that your client does have it installed. Moreover, going further down this tutorial will also require you to have CDO installed on your machines. CDO is not available in redistributable form nor can you deploy CDO.dll with your application. CDO is only available in Office CD and can only be installed using the Microsoft Office MSI setup. There is an example at the end of this tutorial that shows how to invoke MSI programmatically to automatically install the CDO. We'll use the following CDO objects: #import "F:\\Program Files\\Common Files\\System\\MSMAPI\\1033\\cdo.dll" \ rename_namespace("CDO") We can access the current folder of Outlook using Outlook Object Model using First, move back to // 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, till here, we've got the CDO Folder, we can now get Using CDO to see if a Message has securityNow, let's look at how we can see if a message is encrypted/signed. The following KB presents the whole theory: "KB 194623", but I couldn't find it working for my client as there are a lot of email clients and not all of them set the bits described by this KB. It, in fact, states "using these properties to programmatically determine if a message has security on it is unreliable". In order to achieve the results, the only way I am able to find is that every email that has security on it has a special attachment. The attachment contains the encrypted/signed contents that Outlook decrypts/verifies and displays. This attachment has a "p7m" extension with a MIME type of "application/x-pkcs7-mime". And for our solution, this is going to be in the following way:
Now, add a new member function to your class named IsCDOE // 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; } Using CDO to add custom fields to Outlook itemsOutlook Object Model exposes OK, now let's use The following code snippet adds custom fields to each item. // ..... // 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(); } } Customizing the grouping and sorting of items
Outlook now exposes new XML based Views system. You can create your own Views using XML, or you can modify existing Views by altering the XML. The following is the standard XML of Inbox: //
<?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>
We are concerned of the two nodes In order to customize the grouping of items, you can use "User Defined fields" in the "Customize Current View" option in Outlook, and to do it programmatically, we've already added a custom field to items. We just need to change the //
<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>
So, let's get back to code and add a new member function called // 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 /*<groupby> <order> <heading>Encrypted</heading> <prop>http://schemas.microsoft.com/mapi/string /{00020329-0000-0000-C000-000000000046} /Encrypted</prop> <type>string</type> <sort>asc</sort> </order> </groupby>*/ 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(); } Add the following code to the // 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); } Woof, I m tired of typing :O. Well now, it's time to compile, compile your project and if everything goes OK, you'll have your emails sorted in two groups. Adding items to right click menu
Well, I think this is the most awaited section of this tutorial, and most of you will be concerned of this part rather then digging into cryptography logic :). Amit Dey has already explained a lot about adding items to menu and toolbars. If you've not read that article, it's time to read it as I m not explaining the details of CommandBars and all. In order to add an item to the right click menu of Outlook, we'll have to map the First of all, add private members to hold the CComPtr<Office::_CommandBars> m_spCommandbars; //commandbars CComPtr<Office::CommandBarControl> m_pSortButton; // Sort Button Let's first make necessary changes to our // 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)> now create a call back function declartion in your OAddin.h file as follows void __stdcall OnContextMenuUpdate(); Move to your cpp file and add the definition for the function. // void __stdcall COAddin::OnContextMenuUpdate() { MessageBoxW(NULL,L"Hello Menu Update Event",L"Outlook Addin",MB_OK); } Now setup the sink map. // 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),/*dispinterface*/0xf002 ,OnFolderChange,&OnSimpleEventInfo) SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarsEvents),/*dispinterface*/0x1, OnContextMenuUpdate,&OnSimpleEventInfo) END_SINK_MAP() Now, we've settled up everything to sink the interface from the source interface. The best place to sink events is //... 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)); And finally, you are done.
OK, one of the most important thing is that Let's change the code of // 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; } } Till now, if you compile the project, you'll be able to see an item in the right click menu of Outlook. But a control is useless until you add a handler to it. So now, let's get back to our // 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)>, Again, use the extern _ATL_FUNC_INFO OnClickButtonInfo;
Open the cpp file of the class and add the following line to the top: _ATL_FUNC_INFO OnClickButtonInfo =
{CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
Create a callback function. // void __stdcall OnClickButtonSort(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault); // void __stdcall COAddin::OnClickButtonSort(IDispatch* /*Office::_CommandBarButton* */ 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; } } Now, let's sink the event now. We'll sink the event as soon as the new // 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); OK now, compile your project, and play with your menu. If you click it once, the Mail items are sorted automatically, and if you click it again, Mail items are not sorted again. How about changing the state of the menu to checked?
Oh well, more to write :S, and well, more to read as well. OK, earlier while I was developing a solution for my client, a Microsoft MVP (I'm not naming YOU ;)) replied to me that it's not possible to either add an icon to the right click menu items or change their state to checked, and I had to ship the project without these features to my client. Later, I discovered it using Outlook Spy and Amit Dey's article that it's a little tricky but not impossible. OK, in order to change the state of the Menu item to checked, you just need to change the Following is the code of // 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); } NOTE: in order to add an ICON to the Menu item, you can use the // 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 ; If you compile your project, you'll have a complete plug-in that sorts the elements according to security availability on the message. How to use MSI to install CDO programmatically.MSI can be used to install CDO programmatically. In order to use MSI in your C++ project, you'll have to import the MSI.dll into your project. #import "msi.dll" rename_namespace("MSI") MSI requires the feature name and the product code in order to install CDO. The product code can be retrieved from // 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, that's it :). I've tried to give as much description as possible in the tutorial. The sample code provided is a C++ sample of what I did in VB. It is not optimized to give you the best results, moreover you may find some poor COM implementations in the code, but comments are invited :). Thanks. Cheers :). ~Blowin' me up with her love~ References and AcknowledgementsMSKB Articles:
MSDN Articles:
Books:
Other:
| ||||||||||||||||||||||