Building An Outlook COM Add-In With VC /ATL - Lord of the (disp) sinks
(Page 6 of 7 )
Putting together a couple of toolbars and menu items by themselves is not a very useful exercise, unless we can write command handlers that respond to their events (such as when they are clicked). Let's do that now.
We're going to create some simple code to popup a message box whenever our items are clicked on. In a real-life application (such as a customer relationship management tool), COM add-ins can be used to perform an endless variety of important tasks, such as filtering and forwarding mail automatically, etc.
The CommandBarButton control exposes a click event that is triggered when a user clicks a command bar button. We are going to use this event to run code when the user clicks the toolbar buttons or the menu item. For this, our COM object has to implement an event sink interface. For CommandBarButton control, the events dispinterface is _CommandBarButtonEvents. The click method is declared as
//...
//....Office objects typelibrary
//....
[id(0x00000001), helpcontext(0x00038271)]
void Click( [in] CommandBarButton* Ctrl, [in, out] VARIANT_BOOL* CancelDefault);
//....
//...All we have to do is implement sink interfaces that will be called by the event source through regular connection point protocol whenever a menu or a toolbar button is clicked. Our callback method returns a pointer to the source CommandBarButton object, as well as a boolean value that can be used to accept or negate the default action. As for implementing a dispatch sink interface, that's nothing new, and as an ATL programmer you have probably done this a lot of times.
For the uninitiated, ATL provides two template classes, IDispEventImpl<> and IDispEventSimpleImpl<> for ATL COM objects. They provide an implementation of IDispatch. I prefer the lightweight IDispEventSimpleImpl interface, because it doesn't need the extra type library information.
We need to derive our class from IDispEventSimpleImpl<>, set up our sink map, rig up our callback parameters through the _ATL_SINK_INFO structure, and finally call DispEventAdvise and DispEventUnadvise to connect and disconnect from the source interface.
For our toolbar buttons and menu items, if we were to write a single callback method for all events, then once we have a pointer to the CommandBarButton that triggered the event, we can use it's GetCaption method to get the button text, based on which we can perform some selective action. For this sample however, we'll have just one callback per event.
Here's the steps and details to create our code:
1. Derive your class from IdispSimpleEventImpl. The first parameter is typically the child windows ID in the case of ActiveX controls. For us however, this can be any predefined integer that uniquely identifies the event source (in our case the first toolbar button):
class ATL_NO_VTABLE CAddin :
public CComObjectRootEx < CComSingleThreadModel>,
.....
.....
public IDispEventSimpleImpl<1,CAddin,&__uuidof(Office::_CommandBarButtonEvents>2. Setup the callback. Firstly, we must define a callback as follows:
void __stdcall OnClickButton(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);Then we use the _ATL_SINK_INFO structure to describe the callback parameters. Open the addin.h file and add the following declaration right near the top of the file:
extern _ATL_FUNC_INFO OnClickButtonInfo;Open the addin.cpp file and add the following definition:
_ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};OnClickButton is very elementary. Its code looks like this:
void __stdcall CAddin::OnClickButton(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
USES_CONVERSION;
CComQIPtr pCommandBarButton(Ctrl);
//the button that raised the event. Do something with this...
MessageBox(NULL, "Clicked Button1", "OnClickButton", MB_OK);
}3. We will setup the sinkmap using the ATL macros BEGIN_SINK_MAP() and END_SINK_MAP(). The sink map needs to be populated by SINK_ENTRY_XXX. The sink map basically provides the mapping between a dispatch identifier that defines the event and the member function that handles it.
BEGIN_SINK_MAP(CAddin)
SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton, &OnClickButtonInfo)
END_SINK_MAP()Now that everything is in place, we have to connect to and disconnect from the event source with DispEventAdvise() and DispEventUnadvise(). Our CAddin classes OnConnection() and OnDisconnection() methods are just the place to do this. The parameters to DispEventAdvise() and DispEventUnadvise() are the interface to the event source and the desired event source interface respectively.
//connect to event source in OnConnection
// m_spButton member variable is a smart pointer to _CommandBarButton
// that is used to cache the pointer to the first toolbar button.
DispEventAdvise((IDispatch*)m_spButton,&DIID__CommandBarButtonEvents);
//when I'm done disconnect from the event source
//some where in OnDisconnection()
DispEventUnadvise((IDispatch*)m_spButton);Similarly, implement disp sinks for all of your command buttons and menu items. Write handlers to connect to and from them as described above. That's all there is to it.
If everything works without a hitch after you rebuild and run the add-in, then whenever the buttons or menu item is clicked, the respective callback method should be called, thus displaying a message box on the screen.
Next: Conclusion >>
More C++ Articles
More By Amit Dey