Event Sinking with Word

There are 2 ways that event sinking can be performed on Word:

  1. Using the IAdviseSink interface

    To use the IAdviseSink interface you must first write an object that implements this standard interface. This object is then passed to the DAdvise method of a Word Document's IDataObject interface or to the Advise method of a Word Document's IOleObject interface. Refer to the help MS help on IAdviseSink for more information on this interface.

  2. Using ConnectionPoints

    Word provides the following event sources that can be sinked to:
    ApplicationEvents:

    procedure Startup; dispid 1;
    procedure Quit; dispid 2;
    procedure DocumentChange; dispid 3;

    DocumentEvents:
    procedure New; dispid 4;
    procedure Open; dispid 5;
    procedure Close; dispid 6;

    OCXEvents:
    procedure GotFocus; dispid -2147417888;
    procedure LostFocus; dispid -2147417887;

    To start a connection with Word you must get the IConnectionPointContainer for the Word application or document (depending what events you want to sink to). Next query the IConnectionPointContainer for the IConnectionPoint that you wish to use (ApplicationEvents, DocumentEvents or OCXEvents in this case). Once you have the IConnectionPoint use the Advise method to establish the connection.

    There appears to be some limitations with Word's implementation of connection points. When a document is closed in Word, without closing Word itself, Word sends a DocumentEvents.Close message and then an
    ApplicationEvents.DocumentChange message. Then when Word is closed nothing is sent. On the other hand if Word is closed with an open document then it sends a DocumentEvents.Close message and an
    ApplicationEvents.Quit message. Another problem is that Word will send the DocumentEvents.Close message when the user "closes" the document but before the "Do you wish to save changes?" dialog is shown. So if the user then selects cancel the document is never closed but the DocumentEvents.Close
    message was sent.

    Sample Code (StartingConnection)

    uses
      Word_TLB, activex, comobj, ConnectionObject

    // ConnectionObject is the unit containing TWordConnection

    procedure StartWordConnection(WordApp: _Application;
                                  WordDoc: _Document;
                                  var WordSink: TWordConnection);
    var
      PointContainer: IConnectionPointContainer;
      Point: IConnectionPoint;
    begin
      try
        { TWordConnection is the COM object which receives the
          notifications from Word. Make sure to free WordSink when
          you are done with it. }
        WordSink := TWordConnection.Create;
        WordSink.WordApp := WordApp;
        WordSink.WordDoc := WordDoc;

        // Sink with a Word application
        OleCheck(WordApp.QueryInterface(IConnectionPointContainer, PointContainer));
        if Assigned(PointContainer) then
        begin
          OleCheck(PointContainer.FindConnectionPoint(ApplicationEvents, Point));
          if Assigned(Point) then
            Point.Advise((WordSink as IUnknown), WordSink.AppCookie);
        end;

        // Sink with a Word document
        OleCheck(WordDoc.QueryInterface(IConnectionPointContainer, PointContainer));
        if Assigned(PointContainer) then
        begin
          OleCheck(PointContainer.FindConnectionPoint(DocumentEvents, Point));
          if Assigned(Point) then
            Point.Advise((WordSink as IUnknown), WordSink.DocCookie);
        end;
      except
        on E: Exception do
          ShowMessage(E.Message);
      end;
    end;

    Sample Code (Connection Object)
    unit ConnectionObject;

    interface

    uses
      Word_TLB;

    type
      TWordConnection = class(TObject, IUnknown, IDispatch)
      protected
     
        { IUnknown }
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;

        { IDispatch }
        function GetIDsOfNames(const IID: TGUID; Names: Pointer;
                               NameCount, LocaleID: Integer;
                               DispIDs: Pointer): HResult; stdcall;
        function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
        function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
        function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
                        Flags: Word; var Params;
                        VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;

      public
        WordApp: _Application;
        WordDoc: _Document;
        AppCookie, DocCookie: Integer;
      end;

    implementation

    uses
      Windows, ActiveX, Main;

    procedure LogComment(comment: String);
    begin
      Form1.Memo1.Lines.Add(comment);
    end;

    { IUnknown Methods }

    function TWordConnection._AddRef: Integer;
    begin
      Result := 2;
    end;

    function TWordConnection._Release: Integer;
    begin
      Result := 1;
    end;

    function TWordConnection.QueryInterface(const IID: TGUID; out Obj): HResult;
    begin
      Result := E_NOINTERFACE;
      Pointer(Obj) := nil;
      if GetInterface (IID, Obj) then
        Result := S_OK;
      if (not Succeeded(Result)) then
      if IsEqualIID(IID, DocumentEvents) or IsEqualIID(IID, ApplicationEvents) then
      if GetInterface(IDispatch, Obj) then
        Result := S_OK;
    end;

    { IDispatch Methods }

    function TWordConnection.GetIDsOfNames(const IID: TGUID; Names: Pointer;
                                           NameCount, LocaleID: Integer;
                                           DispIDs: Pointer): HResult;
    begin
      Result := E_NOTIMPL;
    end;

    function TWordConnection.GetTypeInfo(Index, LocaleID: Integer;
                                         out TypeInfo): HResult;
    begin
      Pointer(TypeInfo) := nil;
      Result := E_NOTIMPL;
    end;

    function TWordConnection.GetTypeInfoCount(out Count: Integer): HResult;
    begin
      Count := 0;
      Result := E_NOTIMPL;
    end;

    function TWordConnection.Invoke(DispID: Integer; const IID: TGUID;
                                    LocaleID: Integer; Flags: Word;
                                    var Params; VarResult, ExcepInfo,
                                    ArgErr: Pointer): HResult;
    begin
      // This is the entry point for Word event sinking
      Result := S_OK;
      case DispID of
        1: ; // Startup
        2: ; // Quit
        3: ; // Document change
        4: ; // New document
        5: ; // Open document
        6: ; // Close document
      else
        Result := E_INVALIDARG;
      end;
    end;

    end.