[ 1 ] [ 2 ] [  3] [ 4

6. A controlling pre-process hook

Listing 8 shows a flexible variant of listing 7 where the pre-processing hook's return value controls the standard method execution. This is a crude NODEFAULT-like implementation, controlled by the pre-hook’s return value.

FUNCTION Click
  IF THIS.oHook.Click() && Pre hook
    WAIT WINDOW „Click“
    <100 lines of code>
  ENDIF
ENDFUNC

Listing 8. A pre-process hook method (in a separate object) controls the execution of the standard method.

7. Separated pre- and post-process hooks

Listing 9 dramatically improves on Listing 5 because it uses both pre and post hooks, allowing you to completely bracket transformations. Listing 9 uses the following heristic:

        Events are pre-hooked,

        events then call a method, and

        that method is post-hooked.

In this separated "pre" and "post" architecture it is much safer to subclass the event or the method without scope-resolution (::) entanglement.

FUNCTION Click

  *-- Pre-process hook
  IF THIS.oHook.Click()
    THIS.ClickImplementation()
  ENDIF
ENDFUNC
 
FUNCTION ClickImplementation
  *-- The actual click implementation
  WAIT WINDOW „Click“
  <100 lines of code>
  *-- Post-process hook
  THIS.oHook.ClickImplementation()
ENDFUNC

Listing 9. A dramatically more flexible version of Listing 5. The pre-hook is called before the event code, the event code calls a method, and a post hook ends the method. Thus we have both pre- and post-hooks and few of the subclassing problems we'd have if both hooks were in the event code.

I personally think that code structured like that of listing 9 is very slick. Note that the event (like click() and dblclick()) fires the "pre" hook predictably near the beginning of its execution, and the method (the thing invoked by event) fires the "post" hook predictably the end of its execution. Thus we have, in effect, a hook on the way in, and another on the way out. Other advantages:

However I have just one problem with all this: the hooks in Listing 9 are dependent on the hook classes supporting a particular interface. One promising alternative is to vastly simplify the hook interface by having a single hook method to which we pass an appropriate context identifier, like PROGRAM(). This leads to our last implementation topic, which is to use procedural hooks.

8. Separated pre and post-process hooks with a generic interface

Listing 10 shows a variant of Listing 9 that decouples the hook from its implementation.

DEFINE CLASS FullyHookedButton AS CommandButton
  FUNCTION CLICK
    IF oApp.HookMgr( PROGRAM())  && The "Pre" hook
      THISFORM.SomeMethod()
    ENDIF
ENDDEFINE
DEFINE CLASS MyForm AS FORM
  ADD OBJECT oCmd AS FullyHookedButton
  FUNCTION SomeMethod
    << Your code here >>
    THIS.oApp.HookMgr( PROGRAM())  && The "Post" hook
  ENDFUNC
ENDDEFINE

Listing 10, a component with a hook before calling a Click implementation method. The Click implementation method contains its own hook at its conclusion. Note that the hook calls are made to a hook manager method which permits a clean and generic interface.

The key here is the clean and invariant call, THIS.oApp.HookMgr( PROGRAM()), which allows us to have a single hook method serve for all application hooks. Of course, considerable logic may be required within the oApp::HookMgr() method but, on the other hand, this would allow us to table-base the hook operations, which is infinitely more flexible than hard-coding hooks. This mechanism is similar to the one used internally by the VFP Class Browser. More on this in the next article.

Known uses

VFP Class Browser: The VFP Class Browser is a real hookfest. By using its AddIn() method, and by specifying a method to hook, you can permanently register custom behavior for a variety of events. Registering the add-in is persistent because the AddIn() method creates a record in BROWSER.DBF, which lives in your HOME() directory.

Markus Egger's PowerBrowser: The public-domain PowerBrowser tool is an example of how a fully hooked application like VFP's Class Browser can be considerably adapted.

The INTL Toolkit: The INTL Toolkit, which I wrote, uses hook objects throughout.

VFP Example – Addins

The Visual FoxPro class browser can register and invoke addins. These addins are external programs you can create to customize, control, and extend the browser. This makes the browser very flexible and slick! Here we'll examine the browser's addin interface, and then we’ll see a way you can use addins to bring almost limitless customizability and extensibility to your apps.

By the way, there isn’t much original thinking on in this section.  Ken Levy originally created and coded these addin concepts.  I’m just the one who was curious enough to ask him about it, and happy to share what I learned.

A simple addin for browser

To introduce implementing our own addins, we’ll first look at how class browser addins work. The best way to learn all about addins is to create one. Once we understand how addins work, we’ll extract the browser’s addin functionality into a reusable component.

Here is a handy class browser addin that adorns browser with a "Class Backup" button which, when clicked, creates a backup of the current class library. Look at the listing: the addin program receives an object reference parameter, and henceforth the addin has its way with it. As you can see in Listing 11, Browser addins can be wonderfully straightforward to write.

*  Program...........: CLASSBACKUP.PRG
*  Author............: Steven M Black                    
*  Created...........: 9/29/97  10:09:24
*  Copyright.........: (c) SBC / UP!, 1997
*) Description.......:
*    This class browser addin will
*    add a button to the class browser to
*    allow you to quickly create a backup
*    of the VCX containing the currntly
*    selected class.
*  Calling Samples...:
*  Parameter List....:
*  Major change list.:
*======================================================
*-- The class browser addin mechanism passes
*-- a single reference to the browser instance
*-- that invoked this addin.
LPARAMETERS oSource
WITH oSource
  *-- Add a ClassBackup button
  .AddObject( "cmdClassBackup", "ClassBackup")
  *-- Place and size the ClassBackup button
  *-- ...aligned with the others
  .cmdClassBackup.Top= .cmdCleanup.Top
  *-- ...adjacent to the cleanup class button
  .cmdClassBackup.Left= .cmdCleanup.Left+.cmdCleanup.Width
  *-- ...and sized similarly
  .cmdClassBackup.Height= .cmdCleanup.Height
  .cmdClassBackup.Width= .cmdCleanup.Width
ENDWITH
 
DEFINE CLASS ClassBackup AS CommandButton
  Visible=.T.
  TooltipText= "Backup Class Library"
  Caption= "B"   && You want an icon, you provide it
  FontBold= .T.
  StatusBarText= "Creates a backup of the .VCX"
 
  PROCEDURE Release
    RELEASE THIS
 
  PROCEDURE MouseMove
  LPARAMETERS nIndex, nButton, nShift, nXCoord, nYCoord
    IF EMPTY( THIS.Parent.cFilename)
      THIS .ToolTipText= "Backup: No class selected"
      THIS .StatusBarText= "Backup: No class selected"   
    ELSE
      THIS.TooltipText= "Backup class library "+ ;
         THIS.Parent.cFilename
      THIS.StatusBarText= "Creates a backup of Class Library "+ ;
         THIS.Parent.cFilename
     ENDIF
 
    PROCEDURE Click
      IF EMPTY( THIS.Parent.cFilename)
        NODEFAULT
        RETURN
      ENDIF
      WAIT WINDOW "Backing up "+ THIS.Parent.cFilename NOWAIT
      SELE 0
      USE (THIS.Parent.cFileName) ALIAS Temp AGAIN SHARED
      COPY TO ( THIS.Parent.TrimExt(THIS.Parent.cFileName)+".VCX_Backup")
      USE
ENDDEFINE

Listing 11. A simple class browser addin: A useful backup button to protect you from botched changes.

Registering an addin for browser

You register addins with the browser's Addin() method, which adds, modifies, or deletes a record about the addin in BROWSER.DBF. Here's the addin() method’s prototype:

_oBrowser.AddIn( cName [,cProgram] [,cMethod] ;
     [,cFileFilter] [,cPlatform][,cComment])

The browser programming interface is documented in VFP help under "class browser". Addin()’s important parameters are:

(It’s helpful to remember that all these configuration items are stored in individual memo fields in BROWSER.DBF.)

So we could say  _oBrowser.AddIn("VCX backup", "ClassBackUp.PRG") to register our addin, the choice „VCX Backup“ would appear in the browser’s Addins menu, and our backup button would only materialize we’d made this selection.

Or we could wire ClassBackUp.PRG to the browser’s INIT() method like this: _oBrowser.AddIn("VCX backup", "ClassBackUp.PRG", "Init"). Lo and behold, our magic VCX backup button is there every time we use Browser because ClassBackUp.PRG now runs perpetually in browser's init().

Addin Mechanics

But how does the class browser do this? Because most of browser's events contain hook operations that can invoke pre-registered addins. We’ve discussed hook operations before, so let's quickly summarize what we know about hook operations:

Addin Engineering

Engineering addins requires the following things:

Addins can be implemented in a variety of ways depending on how the hook operation is structured. Here are some good tactical principles to guide the design of hook operations.

Keep code short because, among all the other things, brief code increases the likelihood that predictable junctures are proximate to critical junctures.

Events call methods to avoid placing implementation code in the interface.

Events are pre-hooked to allow pre-processing and control over system’s reaction to the event

Methods are post-hooked to allow modifying the products method.

These four points are all in illustrated in the following code for a „Save“ button“

PROCEDURE Click
  * Keep code short: 4 lines of code here
  * Events are pre-hooked
  IF THIS.oAddinManager.AddinMethod( PROGRAM())
    * This hook could curtail further execution
    RETURN
  ENDIF
   * Events call methods
  RETURN xxxxxx.Save()  &&& xxxxxx is any object, really
ENDPROC
PROCEDURE Save()
  * 
 *** <<Implementation code goes here, then... >>
  *
  * Methods are post-hooked.
  IF THIS.oAddinManager.AddinMethod( PROGRAM())
    RETURN
  ENDIF
ENDPROC

Listing 12. A hookfest.

Note that in the code above, the hook operation code is the same in Click() event as it is in the Save() method. This is a nice bonus.

VFP Example – Hook Chains

Here is a simple example of a hook system. Here we have a form with a textbox and a combobox connected by hooks as shown in figure 6.

Some things of note in the sample code:

Hooks are chained. The combobox, for example, chains through a number of objects, first picking up Dropdown() and Interactivechange() behavior, followed by the Form's hooks, which in this case attach a 2-part Rightclick() behavior.

The Textbox's RightClick() is supplemented by the Form's RightClick() and that of the Form's hook, in this case some additional debug choices  (full functionality not wired-in).

Note the GarbageCollect() procedures which are woven into the Release() processes to clean up these pointers.  Without this, the form will never release without CLEAR ALL.  In some versions of VFP, expect a GPF without this pointer clean-up.

Figure 6: The sample provided creates this system. The combobox hook chain starts is composed of two behavior objects before hooking to the form and its hook. The textbox hook chain is similarly extended by the form and its hook object.

[ 1 ] [ 2 ] [ 3 ] [ 4 ]