OLE Events

Some, but not all, OLE automation objects support events. The most prevalent use of OLE is for the automation server (the OLE object) to implement methods that the automation client (the ooRexx OLEObject) invokes. However, it is also possible for the automation client (the ooRexx OLEOjbect) to implement methods that the automation server (the OLE object) invokes.

The methods that the automation client implements are called event methods and the automation server that suports event methods is called a connectable object. The connectable object defines the events it supports by defining the name of the method and its arugments, but does not implement the method. Rather the automation client implements the method. The client asks the automation server to make a connection. If the connection is established, then from that point on whenever one of the defined events occurs, the server invokes the event method on the connected client.

In effect, what is happening is that the automation server is notifying the automation client that some event has occurred and giving the client a chance to react to the event. Any number of clients can be connected to the same connectable object at the same time. Each client will recieve a notification for any event they are interested in. There is no need for the client to receive notifications for evey event. When the client is not interested in an event, the client simply does not implement a method for that event.

The original implementation of OLEObject allowed the Rexx programmer to use events in this way: The programmer defines and implements a subclass of the OLEObject. Within the subclass, the programmer defines and implements the event methods for which she wants to recieve notifications. The progammer has the client make a connection to the automation server at the time the OLEObject object is instantiated by using the WITHEVENTS keyword for the events argument. If the WITHEVENTS keyword is not used during instantiation, then no event connection can be made.

This is relatively easy to understand and a simple example should make this clear. In the following, rather than create a new OLEObject, the programmer defines a subclass of the OLEObject, a WatchedIE class. The WatcheID object is instantiated with events. This tells the OLEObject to make an event connection, if possible. In the subclass, the programmer implements the events he is interested in receiving notifications for.

Example:


-- Instantiate an instance of the subclassed OLEObject
myIE = .WatchedIE~new("InternetExplorer.Application", "WITHEVENTS")
...

-- This class is derived from OLEObject and contains several methods
-- that will be called when certain events take place.
::class 'WatchedIE' subclass OLEObject

-- This is an event of the Internet Explorer */
::method titleChange
  use arg Text
  say "The title has changed to:" text

-- This is an event of the Internet Explorer
::method beforeNavigate2
  use arg pDisp, URL, Flags, TargetFrameName, PostData, Headers, Cancel
  ...

-- This is an event of the Internet Explorer */
::method onQuit
  ...

However, the process described above only allows using events with OLEObject objects that are directly instantiated by the programmer. There are a number of OLE objects that support events, where the OLEObject object is not instantiated by the programmer, but rather is returned from a method invocation. Prior to ooRexx 4.0.0, events could not be used with these objects. In 4.0.0, methods were added to the OLEObject class that allow using events with any OLE object that supports events.

This second process works this way: With an already instantiated object, the programmer can create method objects for any events of interest and use the addEventMethod() method to add the method to the instantiated object. Then the connectEvents() method is used to connect the automation client (the instantiated object in this case) to the connectable OLE automation server.

The following example demonstrates this second process that is available in ooRexx 4.0.0 and onwards.

Example:


  wordApp = .OLEObject~new("Word.Application")
  wordApp~visible = .true
  document = wordApp~documents~Add

  -- Use the isConnectable method to ensure the object supports connections.
  if document~isConnectable then do

    -- Create a method for the OLEEvent_Close event. From the Word documentation
    --and experimentation, it is known that this event has no arguments.
    mArray = .array~new
    mArray[1] = 'say "Received the OLEEvent_Close event."'
    mArray[2] = 'say "  Event has" arg() "parameters."'
    mArray[3] = 'say'

    mClose = .Method~new("OLEEvent_Close", mArray)

    -- Now add this method to the document object.
    document~addEventMethod("OLEEvent_Close", mClose)

    -- Tell the object to make an events connection.
    document~connectEvents
  end

The preceding example brings up one last point that is important to note when defining event methods. It is possible for an event method to have the same name as a normal invocation method of the OLE object. This gives rise to this scenario:

The programmer adds an ooRexx event method to the OLEObject with that name. Then the programmer tries to invoke the normal method. However, the invocation will no longer get forwarded to the unknown() method. Instead the event method by the same name is invoked. This is the case in the above example. The document object has a close() method that is used to close the document. The document also has the close() event method that is used to notify clients that the document is about to close.

To prevent this scenario, when an event method of an OLE object has the same name as a normal method name, the programmer must prepend OLEEvent_ to the method name. The implementation of OLEObject assumes the programer has done so. If the programmer does not name the event methods using this convention, the results are unpredicatable.

Note that only the event method names that have matching normal event names can be prepended with the OLEEvent_prefix. Other event names must not have the prefix. One way to check for this is to use the getKnownEvents() method. This method will return the correct names for all events the OLE object supports.

Example:

This example is a compelete working program. To run it, Microsoft OutLook must be installed. The program demonstrates some of the various methods of the OLEObject that deal with events. The interface to the program is simplistic, but workable.

Once the program starts, the user controls it by creating specific named files in working directory of the program. This could be done for example using echo:

echo " " > stop.monitor
The three specific file names are: stop.monitor, pause.monitor, and restart.monitor. The stop file ends the program. The pause file has the program stop monitoring for new mail, but keep running. The restart file has the program restart monitoring from the paused state.


/* Monitor OutLook for new mail */
  say; say; say 'ooRexx Mail Monitor version 1.0.0'

  outLook = .oleObject~new("Outlook.Application")

  inboxID = outLook~getConstant(olFolderInBox)
  inboxItems = outLook~getNameSpace("MAPI")~getDefaultFolder(inboxID)~items

  if \ inboxItems~isConnectable then do
    say 'Inbox items is NOT connectable, quitting'
    return 99
  end

  inboxItems~addEventMethod("ItemAdd", .methods~printNewMail)
  inboxItems~connectEvents

  if \ inboxItems~isConnected then do
    say 'Error connecting to inbox events, quitting'
    return 99
  end

  monitor = .Monitor~new
  say 'ooRexx Mail Monitor - monitoring ...'
  do while monitor~isActive
    j = SysSleep(1)
    status = monitor~getStatus

    select
      when status == 'disconnect' then do
        inboxItems~disconnectEvents
        say 'ooRexx Mail Monitor - paused ...'
      end
      when status == "reconnect" then do
        inboxItems~connectEvents
        say 'ooRexx Mail Monitor - monitoring ...'
      end
      otherwise do
        nop
      end
    end
    -- End select
  end
  say 'ooRexx Mail Monitor version 1.0.0 ended'

return 0

::method printNewMail unguarded
  use arg mailItem
  say 'You have mail'
  say 'Subject:' mailItem~subject

::class 'Monitor'
::method init
  expose state active

  state = 'continue'
  active = .true
  j = SysFileDelete('stop.monitor')
  j = SysFileDelete('pause.monitor')
  j = SysFileDelete('restart.monitor')

::method isActive
  expose active
  return active

::method getStatus
  expose state active

  if SysIsFile('stop.monitor') then do
    j = SysFileDelete('stop.monitor')
    active = .false
    state = 'quit'
    return state
  end

  if SysIsFile('pause.monitor') then do
    j = SysFileDelete('pause.monitor')
    if state == "paused" then return "continue"

    if state \== 'quit' then do
      state = "paused"
      return 'disconnect'
    end
  end

  if SysIsFile('restart.monitor') then do
    j = SysFileDelete('restart.monitor')
    if state == 'continue' then return state

    if state \== 'quit' then do
      state = 'continue'
      return 'reconnect'
    end
  end

  return 'continue'