HOWTO: Catch Microsoft Word97 Application Events Using VC++

ID: Q183599


The information in this article applies to:


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 we'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 we 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 we want to catch.
          // 3. Call IConnectionPoint::Advise() with the IUnknown
          //    interface of our 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 we're interested in.
          hr = pConnPtContainer->FindConnectionPoint(
             IID_IWord8AppEvents,
             &m_pConnectionPoint
          );
          ASSERT(!FAILED(hr));
    
          // Get the IUnknown interface of our 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 we'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 we are implementing a specific interface that already has a GUID, we need to change ours 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:


  10. 
          COleDispatchDriver m_app;
          IConnectionPoint *m_pConnectionPoint;
          DWORD m_adviseCookie;
          MyEventSink m_myEventSink; 
  11. Add the following line to WordEventsDlg.h right before the class CWordEventsDlg declaration:


  12. 
          #include "MyEventSink.h" 
  13. Open the file MyEventSink.h and find the declaration of the destructor; it will appear as follows:


  14. 
          // Implementation
          protected:
          virtual ~MyEventSink(); 
  15. Move that declaration above the word "Protected" so that the lines of code appear as follows:


  16. 
          virtual ~MyEventSink();
          // Implementation
          protected:
          // virtual ~MyEventSink(); // Or this line may be removed. 
  17. 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:


  18. 
          // 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; 
  19. 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 may 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:


REFERENCES

For more information about creating sink interfaces, and simplifying the connection process, see the following article in the Microsoft Knowledge Base:

For more information about how to catch events in Microsoft Excel, see the following article in the Microsoft Knowledge Base:

Q186427 HOWTO: Catch Microsoft Excel Application Events Using VC++
Q181845 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:
Q152087 SAMPLE: Connpts.exe Implements Connection Points in MFC Apps
(c) Microsoft Corporation 1998, All Rights Reserved. Contributions by Joe Crump, Microsoft Corporation

Additional query words: IConnectionPointContainer IConnectionPoint advise IAdviseSink coclass dispinterface sink


Keywords          : kbcode kbinterop kbole kbAutomation kbMFC kbVC500 kbVC600 kbWord kbGrpDSO kbOffice2000 
Version           : WINDOWS:97; winnt:5.0,6.0
Platform          : WINDOWS winnt 
Issue type        : kbhowto 

Last Reviewed: June 3, 1999