HOW TO: Handle Word Events by Using Visual C++ .NET and MFC

Article translations Article translations
Article ID: 309294 - View products that this article applies to.
This article was previously published under Q309294
Expand all | Collapse all

On This Page

Note Microsoft Visual C++ .NET (2002) supports both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model. The information in this article applies only to unmanaged Visual C++ code.

SUMMARY

This step-by-step article describes how to handle events in Word from an Automation client that is created with Visual C++ .NET by using Microsoft Foundation Classes (MFC). The article illustrates traditional COM event sinking.

Create a C++ Automation Client to Handle Microsoft Word Events

  1. Follow the steps in the "Create an Automation Client" section of the following Microsoft Knowledge Base article to create a basic Automation client:
    307473 HOWTO: Use a Type Library for Office Automation from Visual C++ .NET
    In step 1, type WordEvents1 for the name of the project.

    In step 3, change the ID of the first button to ID_STARTSINK and the caption to Start Event Sink. Add a second button, and then change the ID of the second button to ID_STOPSINK and the caption to Stop Event Sink.

    In step 4, use the Word type library, and select the following Word interfaces:
    • _Application
    • _Document
    • Documents
    • Selection
    • Window
  2. On the Project menu, click Add Class. Select the Generic C++ class in the list of templates, and then click Open.
  3. In the Generic C++ Class Wizard dialog box, type CAppEventListener for the class name, type IDispatch for the base class, and then click Finish.
  4. Replace all of the code in Appeventslistener.h with the following:
    #pragma once
    #include "oaidl.h"
    #include "CApplication.h"
    #include "CDocument0.h"
    #include "CDocuments.h"
    #include "CSelection.h"
    #include "CWindow0.h"
    
    const IID IID_IApplicationEvents2 = 
    {0x000209fe,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
    // Parsed UUID for WordApplicationEvents2 dispinterface.
    class CAppEventListener : public IDispatch
    {
    private:
       int m_refCount;
    
    public:
       //Constructor.
       CAppEventListener();
       //Destructor
       ~CAppEventListener();
    
       /***** IUnknown Methods *****/ 
       STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj);
       STDMETHODIMP_(ULONG) AddRef();
       STDMETHODIMP_(ULONG) Release();
    
       /***** IDispatch Methods *****/ 
       STDMETHODIMP GetTypeInfoCount(UINT *iTInfo);
       STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, 
          ITypeInfo **ppTInfo);
       STDMETHODIMP GetIDsOfNames(REFIID riid,  
          OLECHAR **rgszNames, 
          UINT cNames,  LCID lcid,
          DISPID *rgDispId);
       STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
          WORD wFlags, DISPPARAMS* pDispParams,
          VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
          UINT* puArgErr);
    
       // The following three examples are patterns for the
       //  declaration, definition, and implementation of
       //  event handler methods.
       STDMETHODIMP WindowSelectionChange(CSelection* Sel);
       STDMETHODIMP WindowBeforeRightClick(CSelection* Sel,
          VARIANT_BOOL* Cancel);
       STDMETHODIMP WindowDeactivate(IUnknown* Doc, 
          CWindow0* Wn);
       // End of examples.
    };
    						
    NOTE: The value for the IID_IApplicationEvents2 variable is derived from the Word object library. For more information, see the "Word Application Events" section.
  5. Replace all of the code in Appeventlistener.cpp with the following code:
    #include "stdafx.h"
    #include "AppEventListener.h"
    
    //Constructor.
    CAppEventListener::CAppEventListener()
    {
       m_refCount = 0;
    }
    
    //Destructor.
    CAppEventListener::~CAppEventListener()
    {}
    
    /******************************************************************************
    *   IUnknown Interfaces -- All COM objects must implement, either 
    *  directly or indirectly, the IUnknown interface.
    ******************************************************************************/ 
    
    /******************************************************************************
    *  QueryInterface -- Determines if this component supports the 
    *  requested interface, places a pointer to that interface in ppvObj if it is 
    *  available, and returns S_OK. If not, sets ppvObj to NULL and returns 
    *  E_NOINTERFACE.
    ******************************************************************************/ 
    STDMETHODIMP CAppEventListener::QueryInterface(REFIID riid, void ** ppvObj)
    {
       if (riid == IID_IUnknown){
          *ppvObj = static_cast<IUnknown*>(this);
       }
    
       else if (riid == IID_IDispatch){
          *ppvObj = static_cast<IDispatch*>(this);
       }
    
       else if (riid == IID_IApplicationEvents2){
          *ppvObj = static_cast<IDispatch*>(this);
       }
    
       else{
          *ppvObj = NULL;
          return E_NOINTERFACE;
       }
    
       static_cast<IUnknown*>(*ppvObj)->AddRef();
       return S_OK;
    }
    
    /******************************************************************************
    *  AddRef() -- In order to allow an object to delete itself when 
    *  it is no longer needed, it is necessary to maintain a count of all 
    *  references to this object.  When a new reference is created, this function 
    *  increments the count.
    ******************************************************************************/ 
    STDMETHODIMP_(ULONG) CAppEventListener::AddRef()
    {
       return ++m_refCount;
    }
    
    /******************************************************************************
    *  Release() -- When a reference to this object is removed, this 
    *  function decrements the reference count.  If the reference count is 0, 
    *  this function deletes this object and returns 0.
    ******************************************************************************/ 
    STDMETHODIMP_(ULONG) CAppEventListener::Release()
    {
       m_refCount--;
    
       if (m_refCount == 0)
       {
          delete this;
          return 0;
       }
       return m_refCount;
    }
    
    /******************************************************************************
    *   IDispatch Interface -- This interface allows this class to be used as an
    *   Automation server, allowing its functions to be called by other COM
    *   objects.
    ******************************************************************************/ 
    
    /******************************************************************************
    *   GetTypeInfoCount -- This function determines if the class supports type 
    *   information interfaces or not. It places 1 in iTInfo if the class supports
    *   type information and 0 if it does not.
    ******************************************************************************/ 
    STDMETHODIMP CAppEventListener::GetTypeInfoCount(UINT *iTInfo)
    {
       *iTInfo = 0;
       return S_OK;
    }
    
    /******************************************************************************
    *   GetTypeInfo -- Returns the type information for the class. For classes 
    *   that do not support type information, this function returns E_NOTIMPL;
    ******************************************************************************/ 
    STDMETHODIMP CAppEventListener::GetTypeInfo(UINT iTInfo, LCID lcid, 
                                                ITypeInfo **ppTInfo)
    {
       return E_NOTIMPL;
    }
    
    /******************************************************************************
    *   GetIDsOfNames -- Takes an array of strings and returns an array of DISPIDs
    *   that correspond to the methods or properties indicated. If the name is not 
    *   recognized, it returns DISP_E_UNKNOWNNAME.
    ******************************************************************************/ 
    STDMETHODIMP CAppEventListener::GetIDsOfNames(REFIID riid,  
                                                  OLECHAR **rgszNames, 
                                                  UINT cNames,  LCID lcid,
                                                  DISPID *rgDispId)
    {
       return E_NOTIMPL;
    }
    
    /******************************************************************************
    *   Invoke -- Takes a dispid and uses it to call another of the methods of this 
    *   class. Returns S_OK if the call was successful.
    ******************************************************************************/ 
    STDMETHODIMP CAppEventListener::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
                                           WORD wFlags, DISPPARAMS* pDispParams,
                                           VARIANT* pVarResult,
                                           EXCEPINFO* pExcepInfo,
                                           UINT* puArgErr)
    {
       //Validate arguments.
       if ((riid != IID_NULL))
          return E_INVALIDARG;
    
       HRESULT hr = S_OK;  // Initialize.
    
       switch(dispIdMember){
          case 0x00000001:  //Startup(); Word is started before this is fired.
             OutputDebugString("Startup\n");
             break;
          case 0x00000002:  //Quit();
             OutputDebugString( "Quit\n");
             break;
          case 0x00000003:  //DocumentChange();
             OutputDebugString( "DocumentChange\n");
             break;
          case 0x00000004:  //DocumentOpen([in] Document* Doc);
             OutputDebugString( "DocumentOpen\n");
             break;
          case 0x00000005:  // No dispID
             OutputDebugString( "No dispID yields 00005. This is wierd!\n");
             break;
          case 0x00000006:  // DocumentBeforeClose([in] Document* Doc, [in] VARIANT_BOOL* Cancel);
             OutputDebugString( "DocumentBeforeClose\n");
             break;
          case 0x00000007:  // DocumentBeforePrint([in] Document* Doc, [in] VARIANT_BOOL* Cancel);
             OutputDebugString( "DocumentBeforePrint\n");
             break;
          case 0x00000008:  // DocumentBeforeSave([in] Document* Doc, [in] VARIANT_BOOL* SaveAsUI, [in] VARIANT_BOOL* Cancel);
             OutputDebugString( "DocumentBeforeSave\n");
             break;
          case 0x00000009:  // NewDocument([in] Document* Doc);
             OutputDebugString( "New Document\n");
             break;
          case 0x0000000a:  // WindowActivate([in] Document* Doc, [in] Window* Wn);
             OutputDebugString( "WindowActivate\n");
             break;
    
             // Next 3 illustrate passing parameters to real event handler functions.
    
          case 0x0000000b:  // WindowDeactivate([in] Document* Doc, [in] Window* Wn);
             // The client(in this case, Word) sends arguments to INVOKE
             // in a parameter array, stacked in reverse order, but you
             // need to send them to the called function in not-reverse order.
             if(pDispParams->cArgs!=2)
                return E_INVALIDARG;
             else
             {
                if(pDispParams->rgvarg[0].vt & VT_BYREF)
                {
                   if(pDispParams->rgvarg[0].vt & VT_BYREF)
                   {
                      WindowDeactivate(*(pDispParams->rgvarg[1].ppunkVal),
                         ((CWindow0*)*(pDispParams->rgvarg[0].ppunkVal)));
                   }
                   else
                   {
                      WindowDeactivate(*(pDispParams->rgvarg[1].ppunkVal),
                         ((CWindow0*)(pDispParams->rgvarg[0].punkVal)));
                   }
                }
                else
                {
                   if(pDispParams->rgvarg[0].vt & VT_BYREF)
                   {
                      WindowDeactivate((pDispParams->rgvarg[1].punkVal),
                         ((CWindow0*)*(pDispParams->rgvarg[0].ppunkVal)));
                   }
                   else
                   {
                      WindowDeactivate((pDispParams->rgvarg[1].punkVal),
                         ((CWindow0*)(pDispParams->rgvarg[0].punkVal)));
                   }
    
                }
             }
             break;
          case 0x0000000c:  // WindowSelectionChange([in] Selection* Sel);
             if (pDispParams->cArgs != 1)
                return E_INVALIDARG;
             else{
                if (pDispParams->rgvarg[0].vt & VT_BYREF)
                   WindowSelectionChange( ((CSelection*)*(pDispParams->rgvarg[0].ppunkVal)) );
                else
                   WindowSelectionChange((CSelection*) pDispParams->rgvarg[0].punkVal );
             }
             break;
          case 0x0000000d:  // WindowBeforeRightClick([in] Selection*  Sel, [in] VARIANT_BOOL* Cancel);
             if(pDispParams->cArgs !=2)
                return E_INVALIDARG;
             else
             {
                if(pDispParams->rgvarg[1].vt & VT_BYREF) // The pointer to bool is always by reference.
                {
                   WindowBeforeRightClick( // call the function
                      ((CSelection*)*(pDispParams->rgvarg[1].ppunkVal)),
                      pDispParams->rgvarg[0].pboolVal);
                }
                else
                {
                   WindowBeforeRightClick(  // Call the function.
                      ((CSelection*)(pDispParams->rgvarg[1].punkVal)),
                      pDispParams->rgvarg[0].pboolVal);
                }
             }
             break;
          case 0x0000000e:  // WindowBeforeDoubleClick([in] Selection*  Sel, [in] VARIANT_BOOL* Cancel);
             OutputDebugString( "WindowBeforeDoubleClick\n");
             break;
          default:
             OutputDebugString( "An event with a dispID above 000e\n");
             break;
       }
       return hr;
    }
    
    /************************************************************************************
    *       Sample event handler functions, called from the above switch.
    *       Fill each with the code needed to handle the event according to
    *       the needs and design of your application.
    ************************************************************************************/ 
    STDMETHODIMP CAppEventListener::WindowSelectionChange(CSelection* Sel)
    {
       OutputDebugString("WindowSelectionChange\n");
       return S_OK;
    }
    STDMETHODIMP CAppEventListener::WindowBeforeRightClick(CSelection* Sel, VARIANT_BOOL* Cancel)
    {
       OutputDebugString("WindowBeforeRightClick\n");
       return S_OK;
    }
    STDMETHODIMP CAppEventListener::WindowDeactivate(IUnknown* Doc, CWindow0* Wn)
    {
       OutputDebugString("WindowDeactivate\n");
       return S_OK;
    }
    					
  6. Double-click the ID_STARTSINK control on your dialog box and add the following code to CWordEvents1Dlg::OnBnClickedStartsink:
    void CWordEvents1Dlg::OnBnClickedStartsink()
    {
       // Common OLE-variants. These are easy variants to use for calling arguments.
       COleVariant
          covTrue((short)TRUE),
          covFalse((short)FALSE),
          covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
       COleException e;
    
       CDocuments oDocs;  // oDocs is the Documents Collection.
       CDocument0 oDoc;  // oDoc is the Document object.
       CString str;
       HRESULT hr = S_OK;
    
       try
       {
          // Start Word and get an Application object.
          if(!m_wordApplication.CreateDispatch("Word.Application", &e))
          {
             str.Format("Problem instantiating Word. Error is %ld <0x%08lx>", e.m_sc, e.m_sc);
             AfxMessageBox(str,MB_SETFOREGROUND);
             return;
          }
          else // Success.
          {
             //Make the application visible. 
             m_wordApplication.put_Visible(TRUE);
          }
    
          oDocs = m_wordApplication.get_Documents();
          oDoc = oDocs.Add(covOptional,covOptional,covOptional,covTrue);
    
          //Do not try to hook up more than once.
          if (m_pAppEventListener != NULL)
             return;
    
          // Get IConnectionPointContainer interface for the server.
          IConnectionPointContainer *pConnPtContainer= NULL;
          hr = m_wordApplication.m_lpDispatch->QueryInterface(
             IID_IConnectionPointContainer,
             (void **)&pConnPtContainer );
          if (SUCCEEDED(hr)){
    
             // Find the connection point for events that you are interested in.
             hr = pConnPtContainer->FindConnectionPoint(
                IID_IApplicationEvents2,
                &m_pConnectionPoint
                );
             if (SUCCEEDED(hr)){
    
                //Create a new CAppEventListener.
                m_pAppEventListener = new CAppEventListener();
                m_pAppEventListener->AddRef();
    
                // Set up advisory connection.
                hr = m_pConnectionPoint->Advise(m_pAppEventListener, 
                   &m_dwConnectionCookie);
    
                // Release the IConnectionPointContainer interface.
                pConnPtContainer->Release(); 
             }
          }
       }
       catch(COleException* e)
       {
          str.Format("Error in Try..Catch was 0x%08lx", e->m_sc);
          AfxMessageBox(str, MB_SETFOREGROUND);
          return;
       }
    }
    					
  7. Double-click the ID_STOPSINK control on your dialog box and add the following code to CWordEvents1Dlg::OnBnClickedStopsink:
    void CWordEvents1Dlg::OnBnClickedStopsink()
    {
       //If the connection point has been advised, unadvise it and clean up.
       if (m_pConnectionPoint != NULL){
          m_pConnectionPoint->Unadvise( m_dwConnectionCookie );
          m_dwConnectionCookie = 0;
          m_pConnectionPoint->Release();
          m_pConnectionPoint = NULL;
       }
    
       if (m_pAppEventListener != NULL){
          m_pAppEventListener->Release();
          m_pAppEventListener = NULL;
       }
    
       m_wordApplication.ReleaseDispatch();
    }
    					
  8. Append the following code to CWordEvents1Dlg::OnInitDialog in Wordevents1dlg.cpp:
        m_pAppEventListener = NULL;
        m_pConnectionPoint = NULL;
        m_dwConnectionCookie = 0;
    					
  9. Add the following include to Wordevents1dlg.h:
    #include "AppEventListener.h"
    					
  10. In the Wordevents1dlg.h file, add the following to the list of protected variables in the //Implementation section of CWordEvents1Dlg:
       CApplication m_wordApplication;
       CAppEventListener* m_pAppEventListener;
       IConnectionPoint* m_pConnectionPoint;
       DWORD m_dwConnectionCookie;
    					

Test the Application

  1. Press F5 to build and run the program.
  2. Click Start Events Sink on the form to start Word, create a new Word document, and set up the event handlers.
  3. Test the event handlers. To do this, follow these steps:
    1. Double-click anywhere in the document.
    2. Right-click anywhere in the document.
    3. Save the document.
    4. Close the document.
    5. In Visual Studio, select Other Windows on the View menu, and then select Output to view the Output window. The Output window displays a trace of the events that were handled as well as the order in which the events were handled:
      DocumentChange
      WindowActivate
      WindowBeforeDoubleClick
      WindowSelectionChange
      WindowSelectionChange
      WindowBeforeRightClick
      DocumentBeforeSave
      DocumentBeforeClose
      WindowDeactivate
      DocumentChange
      						
  4. Click Stop Events Sink to stop handling Word events.
  5. Quit your program, and then quit Word.

Word Application Events

To verify the universally unique identifier (UUID) for a set of Word events, follow these steps:
  1. On the Tools menu in Visual Studio .NET, click OLE/COM Object Viewer.
  2. Expand the Type Libraries node.
  3. Double-click Microsoft Word your version Object Library in the type libraries list.
  4. When the ITypeLib Viewer opens with the Word object library displayed, expand the node for coClass Application. Under the coClass Application node, you see ApplicationEvents and ApplicationEvents2. If you are viewing the Word 2002 object library, you also see ApplicationEvents3.
  5. Double-click ApplicationEvents2.
  6. In the right pane of the ITypeLib Viewer, locate the following UUID:
    uuid(000209FE-0000-0000-C000-000000000046)
    This UUID corresponds to the IID_ApplicationEvents2 variable that you declared in Appeventlistener.h.

REFERENCES

For additional information about automating Word, click the following article numbers to view the articles in the Microsoft Knowledge Base:
301659 HOWTO: Automate Microsoft Word to Perform Mail Merge from Visual C# .NET
285333 INFO: Word 2002 MailMerge Event Code Demonstration
For more information, see the following Microsoft Developer Network (MSDN) Web site:
Microsoft Office Development with Visual Studio
http://msdn2.microsoft.com/en-us/library/aa188489(office.10).aspx
(c) Microsoft Corporation 2001, All Rights Reserved. Contributions by Chris Jensen, Microsoft Corporation.

Properties

Article ID: 309294 - Last Review: June 29, 2007 - Revision: 8.3
APPLIES TO
  • Microsoft Visual C++ .NET 2003 Standard Edition
  • Microsoft Visual C++ .NET 2002 Standard Edition
  • Microsoft Office Word 2003
  • Microsoft Word 2002
  • Microsoft Word 2000
Keywords: 
kbautomation kbhowtomaster KB309294

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com