Logo

Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.

EVENTHANDLER()

If you do a lot of work with COM components or servers and want to respond to their events, this function, added in VFP 7, is for you. EVENTHANDLER() lets you bind a VFP object to a COM object, and when an event fires in the COM object, it runs the identically named method in the VFP. This replaces the VFPCOM DLL that was available for download in VFP 6.

Usage

lSuccess = EventHandler( oCOMObject, oVFPEventObject
           [, lUnbind] )

Parameter

Value

Meaning

oCOMObject

Object

The COM object whose events you want to respond to.

oVFPEventObject

Object

A VFP object that has methods matching (implementing) the event names in oCOMObject.

lUnbind

.F. or omitted

Binds the events from one object to the methods in the other. You'll usually omit this parameter because you're binding events together, not unbinding them.

.T.

Unbinds the events. You don't need to explicitly unbind the events; it cleans up after itself if when either one of the objects is released. Oftentimes, it's easier to release an object and re-create it.

lSuccess

.T.

The events are successfully bound.

.F.

The events are not bound.

So, you’ve got this object, oCOMObject, and you want to respond to its events. It could be a control dropped on your form, or it could be an Automation server, perhaps Word or Excel. All you need to do is create your own object that has methods named the exactly the same as the events you want to track.

How do you find out what the events in oCOMObject are named? Well, fortunately the Object Browser will not only tell you what the events are, but it will even write the skeleton of the DEFINE CLASS for your VFP event object! How’s that for service? Simply open the Object Browser, then expand the node labeled Interfaces. In the list of interfaces, look for an interface that looks likely to contain the events. For Word, this interface is ApplicationEvents2. (Apparently, there’s no rule that says interface names have to be informative.) Drag this node to an open editor window, and wherever you drop it, it generates the code. Here’s the code produced for Word’s ApplicationEvents2 interface, though 10 procedure declarations were removed in the interest of keeping the example code short and sweet.

x=NEWOBJECT("myclass")
 
DEFINE CLASS myclass AS session OLEPUBLIC
 
   IMPLEMENTS ApplicationEvents2 IN ;
           "c:\program files\microsoft office\office\msword9.olb"
 
   PROCEDURE ApplicationEvents2_Quit() AS VOID
   * add user code here
   ENDPROC
 
   PROCEDURE ApplicationEvents2_DocumentBeforeSave( ;
           Doc AS VARIANT, SaveAsUI AS LOGICAL, Cancel AS LOGICAL) ;
           AS VOID
   * add user code here
   ENDPROC
 
ENDDEFINE

Notice the IMPLEMENTS keyword. You must implement the events interface in your class, and you have to implement every event. Also note that the path to the type library is hard-coded; you’ll want to make sure that your program can find it on your users’ machines (see DEFINE CLASS for some alternatives ways of handling this). All that’s left for you to do is to add the code that runs when the method is called.

Once you instantiate your VFP event handler and COM objects, issue EVENTHANDLER() to bind the events of the COM object to the same-named methods of the VFP event handler object. The objects stay bound until either of the objects is released. You don’t have to explicitly unbind the objects. However, if you pass .T. as the third parameter, you can unbind them without releasing the objects.

Do pay very close attention to how your variables are scoped. If either the VFP event handler or the COM object is released, EVENTHANDLER() kindly cleans up with no errors or warnings. This is generally considered to be a feature, until you are debugging a situation when one of the objects goes out of scope too soon, leaving the events unbound. To prevent this, incorporate the event handler and the code that uses it into a single class to avoid the problem.

Example

* Get a reference to the COM object.
oWord = CREATEOBJECT("Word.Application")
* Get a reference to your event handler object.
oMyWordEvents = CREATEOBJECT("MyClass")
* Bind them together.
lSuccess = EventHandler(oWord, oMyWordEvents)

* Here's a relatively long example that shows this in
* more detail. It shows some of the events as you
* programmatically move around in an ADO Recordset.
* To actually run this code, you'll need to substitute
* the path of an Access MDB installed on your
* machine into the line starting "oConn.Open" below.
* Define the objects we'll use (for IntelliSense).
LOCAL oConn as ADODB.Connection, ;
      oRS as ADODB.RecordSet
* Create the Connection object and open the Northwind database.
oConn = CreateObject('ADODB.Connection')
oConn.Open('Provider=Microsoft.Jet.OLEDB.4.0;' + ;
           'Data Source=D:\Program Files\Microsoft Visual ' + ;
           'Studio\VB98\Nwind.mdb')
* Create the Recordset and ADOEventHandler objects and
* bind them together.
oRS     = CreateObject('ADODB.RecordSet')
oEvents = CreateObject('ADOEventHandler')
EventHandler(oRS, oEvents)
* Get all records from the Customers table, then move around
* in the Recordset.
oRS.CursorLocation   = 3 &&adUseClient
oRS.CursorType       = 3 &&adOpenStatic
oRS.ActiveConnection = oConn
oRS.Open('select * from Customers')
oRS.MoveFirst()
oRS.MoveNext()
oRS.MoveLast()
* Clean up before we exit.
EventHandler(oRS, oEvents, .T.)
oRS.Close()
oConn.Close()
* ADOEventHandler class definition, created by dragging
* RecordsetEvents interface of ADODB (Microsoft ActiveX
* Data Objects 2.6 Library) from Object Browser to this
* PRG and then modifying it.

DEFINE CLASS ADOEventHandler AS session OLEPUBLIC
   IMPLEMENTS RecordsetEvents IN ADODB.RecordSet

   PROCEDURE RecordsetEvents_WillChangeField( ;
             cFields AS Number, Fields AS VARIANT, ;
             adStatus AS VARIANT @, pRecordset AS VARIANT) ;
             AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_FieldChangeComplete(;
             cFields AS Number, Fields AS VARIANT, ;
             pError AS VARIANT, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_WillChangeRecord(;
             adReason AS VARIANT, cRecords AS Number, ;
             adStatus AS VARIANT @, pRecordset AS VARIANT) ;
             AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_RecordChangeComplete( ;
             adReason AS VARIANT, cRecords AS Number, ;
             pError AS VARIANT, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_WillChangeRecordset(;
             adReason AS VARIANT, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_RecordsetChangeComplete( ;
             adReason AS VARIANT, pError AS VARIANT, ;
             adStatus AS VARIANT @, pRecordset AS VARIANT) ;
             AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_WillMove(;
             adReason AS VARIANT, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
      lcMessage = 'WillMove event:' + chr(13) + 'Reason: '
      * ADO EventReasonEnum values
      #DEFINE ADRSNMOVE         10
      #DEFINE ADRSNMOVEFIRST    12
      #DEFINE ADRSNMOVENEXT     13
      #DEFINE ADRSNMOVEPREVIOUS 14
      #DEFINE ADRSNMOVELAST     15
      DO CASE
         CASE adReason = ADRSNMOVE
            lcMessage = lcMessage + 'move'
         CASE adReason = ADRSNMOVEFIRST
            lcMessage = lcMessage + 'move first'
         CASE adReason = ADRSNMOVENEXT
            lcMessage = lcMessage + 'move next'
         CASE adReason = ADRSNMOVELAST
            lcMessage = lcMessage + 'move last'
      ENDCASE
      WAIT WINDOW lcMessage
   ENDPROC

   PROCEDURE RecordsetEvents_MoveComplete( ;
             adReason AS VARIANT, pError AS VARIANT, ;
             adStatus AS VARIANT @, pRecordset AS VARIANT) ;
             AS VOID
      WAIT WINDOW 'MoveComplete event'
   ENDPROC

   PROCEDURE RecordsetEvents_EndOfRecordset( ;
             fMoreData AS LOGICAL @, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
   * add user code here
   ENDPROC

   PROCEDURE RecordsetEvents_FetchProgress( ;
             Progress AS Number, MaxProgress AS Number, ;
             adStatus AS VARIANT @, pRecordset AS VARIANT) ;
             AS VOID
      WAIT WINDOW 'FetchProgress: ' + TRANSFORM(Progress) + ;
                  ' of ' + TRANSFORM(MaxProgress)
   ENDPROC

   PROCEDURE RecordsetEvents_FetchComplete( ;
             pError AS VARIANT, adStatus AS VARIANT @, ;
             pRecordset AS VARIANT) AS VOID
      WAIT WINDOW 'FetchComplete'
   ENDPROC

ENDDEFINE

See Also

CreateObject, CreateObjectEx(), Define Class