How to catch Word application events by using Visual C++ (183599)



The information in this article applies to:

  • Microsoft Visual C++, 32-bit Enterprise Edition 5.0
  • Microsoft Visual C++, 32-bit Enterprise Edition 6.0
  • Microsoft Visual C++, 32-bit Professional Edition 5.0
  • Microsoft Visual C++, 32-bit Professional Edition 6.0
  • Microsoft Visual C++, 32-bit Learning Edition 6.0
  • Microsoft Word 2002
  • Microsoft Word 2000
  • Microsoft Word 97 for Windows

This article was previously published under Q183599

SUMMARY

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.

MORE INFORMATION

The following steps illustrate how to create an MFC application that catches the Microsoft Word 97 Application events Startup(), DocumentChange(), and Quit():
  1. Create a new dialog box-based application using the MFC AppWizard. Name your project WordEvents, and accept the default settings.
  2. Add two buttons to your dialog box and name the buttons "Start and Setup" and "Quit and Clean Up," respectively.
  3. 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();
    					
  4. 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();
    					
  5. 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.
  6. In the MFC ClassWizard, click the Automation tab and add these three methods in order:
          void Startup()
          void Quit()
          void DocumentChange()
    					
  7. In MyEventSink.cpp, implement these new methods to display message boxes when they are called to let you know when they are triggered:
          void MyEventSink::Startup()
          {
             AfxMessageBox("MyEventSink::Startup() called.");
          }
    
    
          void MyEventSink::Quit()
          {
             AfxMessageBox("MyEventSink::Quit() called.");
          }
    
          void MyEventSink::DocumentChange()
          {
             AfxMessageBox("MyEventSink::DocumentChange() called.");
          }
    					
  8. 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}};
  9. Add the following public member variables to your WordEventsDlg class in WordEventsDlg.h:
          COleDispatchDriver m_app;
          IConnectionPoint *m_pConnectionPoint;
          DWORD m_adviseCookie;
          MyEventSink m_myEventSink;
    					
  10. Add the following line to WordEventsDlg.h right before the class CWordEventsDlg declaration:
          #include "MyEventSink.h"
    					
  11. Open the file MyEventSink.h and find the declaration of the destructor; it will appear as follows:
          // Implementation
          protected:
          virtual ~MyEventSink();
    					
  12. 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.
    					
  13. 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;
    					
  14. 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.

REFERENCES

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 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 Connpts.exe Implements Connection Points in MFC Apps


Modification Type:MinorLast Reviewed:3/21/2005
Keywords:kbProgramming kbAutomation kbcode kbFAQ kbhowto kbinterop KB183599