Hook Operations
By Steven Black
Introduction
In this article, I'll introduce a new design pattern called hook operations, which defines mechanisms to make software flexible and extendible. this is the first design pattern i've ever created from scratch, so I welcome any comments or questions you may have about it.
All software design patterns are born from the need for flexibility. But few patterns explicitly address the issue of how to make software extensible or configurable downstream or at run-time. Furthermore, the more a framework or component is reused, the more clients it serves (by definition) and the greater is the stress on it to adapt. Hook Operations are a way of adapting frameworks or components without stressing them unduly.
Before proceeding, here are some definitions:
Hook: a call within software that's explicitly designated to provide future flexibility.
Hook Operation: code that implements a hook.
Hook Operation Design Pattern: defines interfaces and methodologies for hook operations.
For the most part, the Hook Operation design pattern is applicable in both object and non-object oriented programming. I discuss both, but I only allude to the general implementation mechanisms of procedural hooks, and the code examples presented in this article focus only on object-oriented hooks. I'll cover procedural hooks in a future article.
Name
Hook Operation
Intent
The Hook Operation design pattern defines interfaces and mechanisms by which software is engineered to be flexible and extended by people other than its creators.
Motivation
Toolkits and frameworks are composed of general-purpose classes that are later adapted by others. Toolkits provide for code and component reuse, and frameworks provide for architectural reuse. In both cases, flexibility and extensibility are virtues. How to best provide this adaptability?
In Visual FoxPro, instance programming is one way toolkits and frameworks are sometimes adapted. When you work in the Screen Designer, or in non-class objects in the Class Designer, you are instance programming; that is, you are adding user-code to object instances. This is usually fine and it works, but it means the following disadvantages:
- Source code is required,
- the changes requires recompiling, and
- "programming by exception" (example: "our Atlanta office does things slightly differently") isn't easily accommodated.
Subclassing is another common way toolkits and frameworks are meant to be adapted. Adaptation by subclassing, however, presents similar problems:
- source code is required,
- specialized class knowledge is required to mitigate side-effects,
- the potential for over-enthusiastic subclassing, and
- possibility of creating difficult upgrade, support, and maintenance situations.
Moreover downstream subclassing constrains framework creators in future releases. The framework creators have no control of framework subclassing. This puts framework creators in a difficult position: on one hand they want to perpetually improve their classes, but on the other hand each change may break somebody's code.
Hook operations are usually a viable alternative to instance programming and subclassing.
To illustrate a simple hook, consider a control with the click method in Listing 1. The built-in click method is preceded by the hook operation THIS.PreClickHook(). The PreClickHook() method is a good place for users to place custom processing in subclasses without polluting the built-in Click behavior.
FUNCTION Click THIS.PreClickHook() WAIT WINDOW "Built-in Click Method Starts Now" * <100 lines of code> ENDFUNC
Listing 1. A simple hook. In this example the user can either instance-program or subclass and place code in the PreClickHook() method. Later we'll see other alternatives to implement hooks.
Subclassing, though, can be problematic. If we could implement hooks without subclassing, we would see the following benefits:
- Source code would not be required.
- Modifications would override or augment the original code, but not change it directly.
- Hooks would be less likely to be affected by future changes to the underlying framework classes.
- Adaptation would occur in places specifically designed for this purpose, but outside the class hierarchy, which is easier to support.
Listing 2 shows how we could modify Listing 1 to engineer hooks without subclassing. Instead of delegating to a local method, the hook calls out to an object reference which can be dynamically configured.
FUNCTION Click oSomeObjectReference.ClickHook() WAIT WINDOW "Built-in Click Method Starts Now" * <100 lines of code> ENDFUNC
Listing 2. Another simple hook. In this example the hook is implemented by reference to another separate object.
Hooks usually do nothing by default; they are used by exception, if at all. A hook is a separate and distinct method or process, reserved for adaptation, in a way separates the adaptation from built-in behavior. The hook is usually invoked in such a way that the built-in behavior can be enhanced or replaced without affecting the original source and without altering the built-in behavior provided by the class.
The key thing to take from the above is hook operations are ubiquitous calls to external code and, by default and for the moment, that external code simply does nothing.
Later in this article I'll illustrate 2 sorts of hook operations. I'll just describe them for now:
Object Hooks are hooks that are fulfilled by attaching behavior objects to instances. See figure 1.

Figure 1: Object hooks illustrated, here for a single Click() method. In diagram A the instance is not hooked and functions as normal. In diagram B the instance is hooked by a hook object whose non-blank methods extend (or possibly override) the code in the instance.
Procedural Hooks are hooks that are fulfilled procedurally. This fulfillment may be a straight program call, but it could also be metadata-driven. See figure 2.

Figure 2: A metadata-based procedural hook illustrated. Here a call to a Hook Manager triggers a lookup which returns the name of a program to execute. Since hooks are used by exception, normally the lookup would return nothing.
Applicability -- where to put hooks
Hooks are appropriate in places where you want a program to be adaptable. Where might that be? Two conflicting forces determine this. On one hand, hook operations are most valuable at critical junctures in the program. On the other hand, usability suggests that hooks should be placed at predictable junctures.
In object-oriented programs, critical junctures are commonly adjacent to significant state transformations. For example, in an accounting application, the place where transactions are posted is a critical juncture. One would hope for a hook either before and after such important state transitions.
Critical junctures are difficult to identify, especially in frameworks -- criticality, after all, depends on the implementation. An irrelevant juncture in one application may be crucial in another. For completeness, all plausible junctures (all major state transformations) would need to be hooked (and documented). This is onerous.
Predictable junctures, on the other hand, are architecturally predictable places. For example, predictable junctures could be defined as where methods begin and end. Predictable junctures are sometimes (but not always) located close to critical junctures, and their predictability makes them very usable.
How close are predictable junctures to critical ones? This is a function code volume: The briefer the method code, the likelier predictable junctures are proximate to critical junctures.
The tradeoff between critical and predictable junctures is a developer-education issue. If implementers are not able to identify where hook operations are placed, then they wont be able to engineer the adaptations they require. The main benefit of hooking at predictable junctures is the hooks are much easier to identify and document.
I find it easiest to place hook operations at predictable junctures rather than fret about placing hooks at every critical juncture. But don't recommend hooking all predictable junctures. Every method of every object doesn't need a hook, and since the hook calls involve processing, it's best to use some common sense. Some common events I hook include Click(), DblClick(), RightClick(), DragDrop(), GotFocus() and a few others. For obvious reasons, I don't recommend hooking quasi-continuous events such as MouseMove() and Paint().
Structure
A number of hook structures are possible, depending on how the hook is engineered.
Internally fulfilled hooks: When hooked objects handle fulfill their hooks by instance programming or by subclassing, the structure illustrated in Figure 3 is typical. This classic structure is well known and is the basis for the Template Method design pattern as described by Gamma, Helm et al in their Design Patterns book.
Externally fulfilled hooks: If hook operations delegate to external hook implementations, this leads to many possible variants. Two examples: Figure 4 shows a case where the hook objects come from separate hook classes, and Figure 5 shows a case where the hook class is a superclass of the hooked class.

Figure 3: What I call "self-sufficient hooks" have a structure similar to that of the Template Method design pattern. Here you use inheritance or instance programming to modify the default behavior.

Figure 4: Supporting hooks are separate hook objects, here shown as separate classes from those they adorn.

Figure 5: Hooked objects and their hooks can share the same interface, which implies that they can share the same class hierarchy. This is very handy from a maintenance perspective since the interfaces remain automatically in synch.
Figure 5 deserves explanation because it is so counterintuitive and admittedly convoluted. One would think that a hook couldn't possibly be from a Parentclass of what we wish to modify. But it works great! Here's why:
Hooked objects use the public interface of hook objects. Therefore giving both a common interface makes sense because of the polymorphic nature of the linkages. Furthermore if they are of the same class, then keeping the interfaces in synch is a cinch.
If a hooked object is-a hook, then the concrete objects can use each other as hooks. Effectively, this means they can call each other's services as hook operations. For example, all the controls on a form could use the form's RightClick() services, preserving the RightClick() context of the form whilst the cursor is on a control.
Since hooked objects can call hooks, and hook objects are hooks, this means they can be chained together. (I'll explain this later in the Implementation section).
Structure: Hook sequencing
Assuming we are free to sprinkle hook operations throughout our events and methods, how to best place them? To help answer this, here are our placement choices relative to the built-in behaviors we may wish to adapt:
Pre-process hooks are invoked before the usual procedure code. Pre-process hooks are especially handy if they can override (or pre-empt or replace) built-in behavior.
Post-process hooks are invoked after the usual procedure code. Unlike pre-process hooks, post-process hooks cannot control the execution of the procedure which invokes it.
Mid-process hooks are invoked in mid-procedure. I won't discuss this option further, but bear in mind that these can be useful, especially within monolithic procedures.
Participants
There are two participants in the hook operation pattern. Note that the pattern can be extended, so one participant may play both roles.
Hooked Object
Contains a template method which contains one or more hook operations.
Hook Object
Not necessarily distinct from the Hooked Object
Implements the hook
Collaborations
The hooked object hook methods through hook operations.
Hook objects may themselves be hooks.
Consequences
The advantages of Hook Operations include:
- Downstream developers can modify application behavior by adapting hook methods.
- Depending on the architecture, hook objects may be chained together.
Some disadvantages are
- Hook operations are unconventional, and add somewhat to overall application complexity.
- Hook messaging overhead has performance expenses.
Implementation
This section briefly shows some ways hook operations can be implemented in Visual FoxPro
1. A simple pre-process hook
Listing 3 shows a simple call to a pre-process hook. This example uses the common convention of naming the pre-process with the prefix "pre", and similar hook operations are found in a number of VFP frameworks.
FUNCTION Click *-- Pre-process hook THIS.PreClick() WAIT WINDOW "Click" * <100 lines of code> ENDFUNC
Listing 3. A pre-hook followed by standard method code.
2. A simple post-process hook
Listing 4 shows a simple post-processing hook. This example uses the common convention of naming the pre-process with the prefix "post"
FUNCTION Click WAIT WINDOW "Click" * <100 lines of code> *-- Post-process hook THIS.PostClick() ENDFUNC
Listing 4. A post-process Click hook.
3. Combined simple pre and post-process hooks
As you may have guessed, pre and post-hooks can be combined, as shown in listing 5, in an attempt to achieve flexibility on both sides of the standard call. This is not recommended!
FUNCTION Click THIS.oHook.PreClick() && Pre-hook WAIT WINDOW "Click" * <100 lines of code> THIS.oHook.PostClick() && Post-hook ENDFUNC
Listing 5. Pre and post-hooks surround a standard method. If you intend this method to be subclassed, then avoid this!
However you do it, combining pre and post-hooks, as in listing 5, is not recommended if the method is to be subclassed. If you subclass the click method of listing 5, and use DODEFAULT() or the scope resolution operator (::) to invoke this parent method, then youll fire both the pre and post-hook at that point. Depending on the hooks, this can cause unexpected results because both hooks will fire as part of the scope resolution invocation.
4. A delegated pre-process hook
In a generalization of Listing 3, we could substitute THIS.oHook to reference a specialized hook object, as illustrated in Listing 6.
FUNCTION Click *-- Pre-process hook THIS.oHook.PreClick() WAIT WINDOW "Click" * <100 lines of code> ENDFUNC
Listing 6. A pre-hook method belonging to a hook object. This gives us flexibility to substitute behavior objects, as well as accomplish the same thing as Listing 3 by setting THIS.oHook=THIS.
5. A simple polymorphic pre-hook
Listing 7 improves on Listings 3 and 6 because it uses a clean implementation heuristic: Hooks are called polymorphically. In other words, the Click() method of an object calls the Click() method of its hook.
FUNCTION Click *-- Pre-process hook THIS.oHook.Click() WAIT WINDOW "Click" * <100 lines of code> ENDFUNC
Listing 7. A cleaner version of Listings 3 and 6. Hook methods are called polymorphically.
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-hooks 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:
- Code in the user-interface object's events is kept to a minimum, thus making it easier to reuse.
- The behavior of the user-interface object can be totally controlled by the pre-process hook.
- Pre and post hooks can coexist in the same process without any inherent subclassing problems.
- The locations of the all such hooks -- here and elsewhere -- is easily predictable with a simple heuristic: Events fire "pre" hooks, events then fire methods, and the methods fire "post" hooks..
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 identifyer, 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.
Visual FoxPro Example
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.
*---------------------------------------------
*-- Sample for Hook Operation design pattern
*-- Copyright 1997 by Steven Black
*-- Version 1.0 May 22 1997
*---------------------------------------------
*-- Create a hookable form
oForm= Create("Frm")
*-- Describe what the user should do
oForm.Caption= "RightClick around to execute hooks"
*-- add a hookable textbox
oForm.AddObject("txt", "Txt")
*-- add a hookable combobox
oForm.AddObject("Cbo", "Cbo")
oForm.Cbo.Top= 30
*-- add dropdown and interactivechange behavior to
*-- combobox by using hooks
Local loTemp
loTemp= Create("DropDown")
oForm.Cbo.SetoHook( loTemp)
loTemp= Create("Interactivechange")
oForm.Cbo.SetoHook( loTemp)
Release loTemp
oForm.Cbo.ToolTipText= ;
"Make a selection to execute hooks"
oForm.Cbo.StatusBarText= ;
"Make a selection to execute hooks"
*-- Give all controls the Form's Right-click behavior
oForm.txt.SetoHook( oForm)
oForm.Cbo.SetoHook( oForm)
*-- add a general shared debug utility object
oForm.AddObject( "Debug", "DebugMenuHook")
oForm.Debug.Top= 60
*-- Give all controls the debug menu behavior
*-- (via form, which is already in the hook chain)
oForm.SetoHook( oForm.Debug)
*-- Show the works, though you wouldn't normally
*-- make the debug hook object visible
oForm.SetAll("Visible", .T.)
oForm.Show()
Read Events
************************************************** * Abstract Hook Behavior Class ************************************************** *-- Class: abstracthook *-- ParentClass: label *-- BaseClass: label *-- Baseclass for hooks * Define Class abstracthook As Label Caption = "Hook" ohook = .Null. Width= 150 BackColor= Rgb(0,255,0) Name = "abstracthook" Procedure Init *-- Say something smart This.Caption= This.Class Endproc Procedure Release *-- Release from memory. Release This Endproc
Procedure GarbageCollect
*-- Clean up pointers
This.ohook= .Null.
Endproc
*=== Hooked Events and Methods ====
* SetoHook( o)
* RightClick()
* PopUpMenu()
* InteractiveChange()
* DropDown()
Procedure SetoHook
*-- Automate the hook setting process. If a hook
*-- already exists, hook to it, thus adding to the
*-- hook chain.
Parameters toPassed
If Type( "THIS.oHook")= "O" And ;
PEMSTATUS( This.ohook, "oHook", 5)
This.ohook.SetoHook( toPassed)
Else
This.ohook= toPassed
Endif
Endproc
Procedure RightClick
If Type( "THIS.oHook")= "O" And ;
PEMSTATUS( This.ohook, "RightClick", 5)
This.ohook.RightClick
Endif
Release Popups Shortcut
Define Popup Shortcut Shortcut Relative From Mrow(),Mcol()
This.PopupMenu()
If Cntbar( "Shortcut") > 0
Activate Popup Shortcut
Endif
Endproc
Procedure PopupMenu
*-- Delegate to the implementation chain
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "PopUpMenu", 5)
Return This.ohook.PopupMenu()
Endif
Endproc
*-- Occurs when the user changes the value of a
*-- control using the keyboard or the mouse.
Procedure InteractiveChange
*-- Delegate to the implementation chain
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "InteractiveChange", 5)
Return This.ohook.InteractiveChange()
Endif
Endproc
*-- Occurs when the list portion of a ComboBox
*-- control is about to drop down after the drop-down ;
*-- arrow is clicked.
Procedure DropDown
*-- Delegate to the implementation chain
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "DropDown", 5)
Return This.ohook.DropDown()
Endif
Endproc
Enddefine
**************************************************
* Developer's Debug Menu Behavior (partial)
**************************************************
*-- Class: debugmenuhook
*-- ParentClass: abstracthook
*-- BaseClass: label
*-- Hook that produces debug choices in the
*-- current shortcut menu
*
Define Class debugmenuhook As abstracthook
Name = "debugmenuhook"
Procedure PopupMenu
DoDefault()
Define Bar 1001 Of Shortcut Prompt "\-"
Define Bar 1002 Of Shortcut Prompt "Debug" ;
MESSAGE "Invokes the debug window"
Define Bar 1003 Of Shortcut Prompt "\* 0
Activate Popup Shortcut
Endif
Endproc
Procedure PopupMenu
*-- Delegate to the implementation chain
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "PopUpMenu", 5)
Return This.ohook.PopupMenu()
Endif
Endproc
Procedure DblClick
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "DblClick", 5)
This.ohook.DblClick
Endif
Endproc
Procedure Click
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "Click", 5)
Return This.ohook.Click()
Endif
Endproc
Procedure DropDown
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "DropDown", 5)
Return This.ohook.DropDown()
Endif
Endproc
Procedure InteractiveChange
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "InteractiveChange", 5)
Return This.ohook.InteractiveChange()
Endif
Endproc
Enddefine
**************************************************
*-- Class: frm
*-- ParentClass: form
*-- BaseClass: form
*-- Form with Rightmouse Behavior
*
Define Class frm As Form
DoCreate = .T.
Caption = "Hooked Form"
ohook = .Null.
AutoCenter= .T.
Name = "frm"
ShowTips= .T.
Procedure Release
*-- Release from memory
This.GarbageCollect()
Release This
Endproc
Procedure GarbageCollect
*-- Clean up pointers
*-- ... recursively
Local lni
For lni= 1 To This.ControlCount
This.Controls[lni].GarbageCollect()
Endfor
This.ohook= .Null.
Endproc
Procedure QueryUnload
*-- Clean up first
This.GarbageCollect()
Endproc
Procedure Destroy
Clear Events
Endproc
*=== Hooked Events and Methods ====
* SetoHook( o)
* RightClick()
* PopUpMenu()
* InteractiveChange()
* DropDown()
Procedure SetoHook
*-- Automate the hook setting process. If a hook
*-- already exists, hook to it, thus adding to the
*-- hook chain.
Lparameters txPassed
If !Isnull( This.ohook)
This.ohook.SetoHook( txPassed)
Else
This.ohook= txPassed
Endif
Endproc
Procedure RightClick
*-- Define a shortcut
Release Popups Shortcut
Define Popup Shortcut Shortcut Relative ;
FROM Mrow(),Mcol()
*-- Delegate to a shortcut specialty method
This.PopupMenu()
*-- Activate the shortcut
If Cntbar( "Shortcut") > 0
Activate Popup Shortcut
Endif
Endproc
Procedure PopupMenu
*-- 3 sample shortcut menu bars
Define Bar 100 Of Shortcut Prompt "Properties" ;
MESSAGE "Display the properties sheet"
Define Bar 200 Of Shortcut Prompt "Builders" ;
MESSAGE "Invoke custom builders"
Define Bar 300 Of Shortcut Prompt "Code" ;
MESSAGE "Edit underlying code"
*-- Delegate to the implementation chain
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "PopUpMenu", 5)
Return This.ohook.PopupMenu()
Endif
Endproc
Procedure DropDown
*-- Occurs when the list portion of a ComboBox
*-- control is about to drop down after the
*-- drop-down arrow is clicked.
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "DropDown", 5)
Return This.ohook.DropDown()
Endif
Endproc
Procedure InteractiveChange
*-- Occurs when the user changes the value ;
*-- of a control using the keyboard or the mouse.
*-- Fire pre-process hook
If ! Isnull( This.ohook) And ;
TYPE( "THIS.oHook") = "O" And ;
PEMSTATUS( This.ohook, "InteractiveChange", 5)
Return This.ohook.InteractiveChange()
Endif
Endproc
Enddefine
**************************************************
*-- Class: txt (c:\_workshop\hooks.vcx)
*-- ParentClass: textbox
*-- BaseClass: textbox
*-- Textbox with Rightmouse Behavior
*
Define Class txt As TextBox
Height = 23
Width = 100
*-- Hook reference
ohook = .Null.
Name = "txt"
Procedure GarbageCollect
*-- Clean up pointers
This.ohook= .Null.
Endproc
*-- Releases from memory.
Procedure Release
Release This
Endproc
Procedure PopupMenu
Define Bar _Med_cut Of Shortcut Prompt "Cu\* "U"
This.ohook.SetoHook( toPassed)
Else
This.ohook= toPassed
Endif
Endproc
Procedure Click
If ! Isnull( This.ohook)
This.ohook.Click
Endif
Endproc
Procedure RightClick
Release Popups Shortcut
Define Popup Shortcut Shortcut Relative ;
FROM Mrow(),Mcol()
This.PopupMenu()
If Cntbar( "Shortcut") > 0
Activate Popup Shortcut
Endif
Endproc
Procedure DblClick
If ! Isnull( This.ohook)
This.ohook.DblClick
Endif
Endproc
Enddefine
When you rightclick over the textbox, control is passed to the hook, which invokes a context sensitive shortcut menu. Some other things to draw from this example:
The hooked objects and the hooks dont need to be of the same Baseclass. Hooks can be lightweight objects, like labels, lines, or even Relations (which are ultra-lightweight).
Hooks can be shared by more than one object. There is nothing stopping us from having all the controls on our form pointing to the same hook.
Conclusion
Classes with code embedded with hook operations are generally more flexible than those that arent. If you define hook operations consistently and predictably throughout your application, then its usually possible to attach new behavior to satisfy the needs of a particular instance without subclassing. Moreover, hook operations can be reckoned and bound at run time.
Steven Black