This article demonstrates how to catch Microsoft Word 97
application events using Microsoft Visual C++. However, the concepts and code
in this article are not specific to Microsoft Word; they are applicable to the
entire suite of Microsoft Office applications, as well as any other
applications that expose events.
The following steps illustrate how to create an MFC
application that catches the Microsoft Word 97 Application events Startup(),
DocumentChange(), and Quit():
Create a new dialog box-based application using the MFC
AppWizard. Name your project WordEvents, and accept the default settings.
Add two buttons to your dialog box and name the buttons
"Start and Setup" and "Quit and Clean Up," respectively.
Add the following code to a handler for the "Start and
Setup" button:
// Check to see if you've already started the server.
if(m_app.m_lpDispatch != NULL) {
AfxMessageBox("Server already started.");
return;
}
char buf[256]; // General purpose buffer.
// Start Automation server.
COleException e;
if(!m_app.CreateDispatch("Word.Application.8", &e)) {
sprintf(buf, "Error on CreateDispatch(): %ld (%08lx)",
e.m_sc, e.m_sc);
AfxMessageBox(buf, MB_SETFOREGROUND);
return;
}
// Make server visible through automation.
// I.e.: Application.Visible = TRUE
DISPID dispID;
unsigned short *ucPtr;
BYTE *parmStr;
ucPtr = L"visible";
m_app.m_lpDispatch->GetIDsOfNames(
IID_NULL, &ucPtr, 1, LOCALE_USER_DEFAULT, &dispID
);
parmStr = (BYTE *)( VTS_VARIANT );
m_app.InvokeHelper(
dispID, DISPATCH_METHOD | DISPATCH_PROPERTYPUT, VT_EMPTY,
NULL, parmStr, &COleVariant((short)TRUE)
);
// Declare the events you want to catch.
// {000209F7-0000-0000-C000-000000000046}
static const GUID IID_IWord8AppEvents =
{0x000209f7,0x000,0x0000,{0xc0,0x00,0x0,0x00,0x00,0x00,0x00,0x46 } };
// Steps for setting up events.
// 1. Get server's IConnectionPointContainer interface.
// 2. Call IConnectionPointContainerFindConnectionPoint()
// to find the event you want to catch.
// 3. Call IConnectionPoint::Advise() with the IUnknown
// interface of your implementation of the events.
HRESULT hr;
// Get server's IConnectionPointContainer interface.
IConnectionPointContainer *pConnPtContainer;
hr = m_app.m_lpDispatch->QueryInterface(
IID_IConnectionPointContainer,
(void **)&pConnPtContainer
);
ASSERT(!FAILED(hr));
// Find connection point for events you're interested in.
hr = pConnPtContainer->FindConnectionPoint(
IID_IWord8AppEvents,
&m_pConnectionPoint
);
ASSERT(!FAILED(hr));
// Get the IUnknown interface of your event implementation.
LPUNKNOWN pUnk = m_myEventSink.GetInterface(&IID_IUnknown);
ASSERT(pUnk);
// Setup advisory connection!
hr = m_pConnectionPoint->Advise(pUnk, &m_adviseCookie);
ASSERT(!FAILED(hr));
// Release IConnectionPointContainer interface.
pConnPtContainer->Release();
Add the following code to a handler for the "Quit and Clean
Up" button:
// Check if you've started the server.
if(m_app.m_lpDispatch == NULL) {
AfxMessageBox("You haven't started the server yet.");
return;
}
m_pConnectionPoint->Unadvise(m_adviseCookie);
// Tell server to quit.
// Application.Quit()
DISPID dispID; // Temporary DISPID
unsigned short *ucPtr; // Temporary name holder
ucPtr = L"quit";
m_app.m_lpDispatch->GetIDsOfNames(
IID_NULL, &ucPtr, 1, LOCALE_USER_DEFAULT, &dispID
);
m_app.InvokeHelper(dispID, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
// Release application object.
m_app.ReleaseDispatch();
Start the MFC ClassWizard (CTRL+W), and add a new class
derived from CCmdTarget and with automation support (check the "Automation"
option). Name this class MyEventSink; it will be our implementation of
Microsoft Word's Application events.
In the MFC ClassWizard, click the Automation tab and add
these three methods in order:
void Startup()
void Quit()
void DocumentChange()
In MyEventSink.cpp, implement these new methods to display
message boxes when they are called to let you know when they are triggered:
Open your MyEventSink.cpp file and find the declaration for
IID_IMyEventSink. The ClassWizard generated a new random GUID for your
interface, but because you are implementing a specific interface that already
has a GUID, you need to change yours to match. Modify the declaration for
IID_IMyEventSink as follows: static const GUID IID_IMyEventSink =
{0x000209f7,0x000,0x0000,{0xc0,0x00,0x0,0x00,0x00,0x00,0x00,0x46}};
Add the following public member variables to your
WordEventsDlg class in WordEventsDlg.h:
Move that declaration above the word "Protected" so that
the lines of code appear as follows:
virtual ~MyEventSink();
// Implementation
protected:
// virtual ~MyEventSink(); // Or this line may be removed.
Finally, make sure the OLE/COM libraries get a chance to
initialize. Add the following code right before your "Start and Setup" button
handler. This creates a global class that gets created at application startup,
and destroyed at exit. The constructor and destructor of this class provide a
handy way to perform initialization and cleanup:
// Ole-initialization class.
class OleInitClass {
public:
OleInitClass() {
OleInitialize(NULL);
}
~OleInitClass() {
OleUninitialize();
}
};
// This global class calls OleInitialize() at
// application startup, and calls OleUninitialize()
// at application exit.
OleInitClass g_OleInitClass;
Compile and run.
After running the application, click the "Start and Setup" button to start Microsoft Word and set up event notifications.
In Microsoft Word, on the File menu, click New to create a new document. Your DocumentChange() event should get
fired. Open another document, and notice that it also gets fired when you
switch activation from one document to another. You can click the "Quit and Clean Up" button to stop the event notifications and quit Microsoft Word,
or you can Exit from Microsoft Word (on the File menu, click Exit) and notice the Quit notification.
You might notice
that the Startup event is never triggered. This is because it fired before you
set up the events. Note that there is really no reason to handle this event,
because the application must have been started before you could call and set
Automation methods and properties.
Microsoft Excel supports many
interesting and helpful events, and you can follow the steps here to catch
them. However, there are a few points to remember:
Use the OLE/COM Object Viewer that comes with Microsoft
Visual C++ 5.0 to view the type library of the server you are interested in. To
find the events, open the coclass declarations (usually at the bottom of the
tree); the associated events for each coclass will be listed. When you click an
event interface you can see what events are available, their DISPIDs, and how
they are declared in the view on the right.
It was not necessary to modify the DISPIDs for our methods
in our MyEventSink class because Microsoft Word's Application events,
Startup(), Quit(), and DocumentChange() have DISPIDs 1, 2, and 3, respectively.
If you create these methods in order, you don't have to modify them to match
the type library because ClassWizard starts at DISPID 1. However, most events,
such as Microsoft Excel's Workbook events, do not start with DISPID 1. In such
cases, you must explicitly modify the dispatch map in MyEventSink.cpp to match
the DISPIDs with the correct methods.
For more information about creating sink interfaces, and
simplifying the connection process, click on the article number below to view
it in the Microsoft Knowledge Base:
181845
(http://support.microsoft.com/kb/181845/
)
HOWTO: Create a Sink Interface in MFC-Based COM Client
For a general example of, and more information about, connection
points, see the Connpts.exe sample described in the following article in the
Microsoft Knowledge Base:
152087
(http://support.microsoft.com/kb/152087/
)
Connpts.exe Implements Connection Points in MFC Apps