BINDEVENT() to Windows message notifications
The VFP 8 BINDEVENT() implementation required that the source object passed as the first parameter be a VFP object. In VFP 9, BINDEVENT() has been enhanced to support binding to Windows message notifications – messages from the Windows operating system itself, outside VFP.
BINDEVENT() enhancements for VFP 9
The parameters for the BINDEVENT(), UNBINDEVENTS(), and AEVENTS() functions have been overloaded in VFP 9 to support new behaviors. The RAISEEVENT() function has not been modified, because it only works with VFP objects, whereas this new feature supports binding to events from the Windows operating system, outside VFP.
Be sure to review the BINDEVENT() help topic in VFP 9. The Windows message Event Handling topic in the Language Enhancements node of the What's New In Visual FoxPro section of the VFP 9 help is also a must-read. By the time VFP 9 ships, you should be able to find some nifty examples in the Solution Samples in the Task Pane.
BINDEVENT(hWnd,…,…,…,…)
The first parameter can now optionally be an Integer value specifying the hWnd window handle for a window. hWnd is the identifier that Windows itself uses to distinguish one window from another, and to send messages.
_Screen, _VFP, ActiveX controls, and the VFP base class Form all have an hWnd property; whereas each individual control in other Windows applications is a separate "Windows window", VFP controls are not "Windows windows". The VFP IDE windows such as the Properties Sheet, Command Window, etc., are also "Windows windows", but they do not expose the hWnd property directly. You can get the hWnd handle, but it takes a bit of work.
When you pass the first BINDEVENT() parameter as an integer, VFP assumes the integer is a valid hWnd; if not, it doesn't generate an error, but, of course, nothing happens in your code, either.
BINDEVENT(…,nMessage,…,…,…)
When the first parameter is passed as an integer hWnd value, the second parameter must also be an integer, the Windows message that the Windows operating system sends to the hWnd as various events occur in the system.
One place you can find the integer values for the Windows message notifications in the WinUser.H file. I found a WinUser.H in my \Program Files\Microsoft Visual Studio .NET\ VC7\PlatformSDK\Include folder.
For example, you can pass the WM_ACTIVATEAPP constant (0x001B) as the second BINDEVENT() parameter, whereupon when the window specified in the first parameter is notified of the WM_ACTIVATEAPP Window Message event, VFP calls the delegate object.method specified in the third and fourth BINDEVENT() parameters.
If you pass invalid integers to the first and/or second BINDEVENT() function, VFP still RETURNs 1, but nothing is really bound. AEVENTS() even lists it as a valid binding in effect.
You can only bind one hWnd+nMessage pair; subsequent BINDEVENT()s with the same window and message replace the existing binding. This is different from the "normal" BINDEVENT() between VFP objects, where you can bind the source to as many delegates as you want. More on this later.
Binding to an hWnd+nMessage in this manner means that when VFP receives a Windows message notification from the Windows operating system, you can execute some VFP code. This is very powerful, allowing us to "trap" many Windows events with relative ease.
This is very powerful, allowing us to "trap" many Windows events with relative ease
BINDEVENT(…,…,Object,MethodName,…)
These two parameters have not changed to support this new behavior. They specify the method of the (VFP) object that VFP calls whenever notified of the Windows message event.
BINDEVENT(…,…,…,…,0/1/2/3)
The fifth nFlags parameter is ignored for Windows message notification bindings, at least for now. One spec available says: "Reserved for future use."
UNBINDEVENTS() enhancements for VFP 9
You can unbind a single hWnd+nMessage pair by passing them to UNBINDEVENTS(). Pass just the hWnd integer to unbind all the bindings for that hWnd. Pass 0/zero to unbind all bindings for all hWnd windows.
AEVENTS() enhancements for VFP 9
You can now pass the second parameter to AEVENTS() as 1 to get information about hWnd+nMessage bindings. The third AEVENTS() parameter does not apply to Windows message notifications.
When you pass 1 as the second parameter, VFP fills the passed 2-dimensional array with as many rows as there are bindings. The array has four columns:
- hWnd (Integer)
- Window Message (Integer)
- Reference of handler (Object)
- Handler delegate (String)
Simple WM_Test.PRG
Here is a very simple example of binding delegate VFP code to the WM_ACTIVATEAPP Windows message notification to the main VFP window.
* WM_Test.PRG
CLEAR ALL
CLOSE ALL
UNBINDEVENTS(0)
PUBLIC goWM_Test
goWM_Test = CREATEOBJECT("cusWM_Test")
DEFINE CLASS cusWM_Test AS Custom
nCallWindowProc = 0
PROCEDURE Init
DECLARE INTEGER GetWindowLong IN WIN32API ;
INTEGER nhWnd, INTEGER nIndex
#DEFINE GWL_WNDPROC (-4)
THIS.nCallWindowProc = ;
GetWindowLong(_VFP.HWND, GWL_WNDPROC)
#DEFINE WM_ACTIVATEAPP 0x001C
* here's the BINDEVENT():
BINDEVENT(_VFP.hWnd,WM_ACTIVATEAPP,THIS,"HandleEvent")
ENDPROC
PROCEDURE HandleEvent
LPARAMETERS nHWnd as Integer, nMsg as Integer, ;
wParam as Integer, lParam as Integer
IF m.wParam = 1
? PROGRAM(), "activate"
ELSE
? PROGRAM(), "deactivate"
ENDIF
DECLARE INTEGER CallWindowProc IN WIN32API
INTEGER lpPrevWndFunc, INTEGER nHWnd, ;
INTEGER nMsg, INTEGER wParam, INTEGER lParam
RETURN CallWindowProc(THIS.nCallWindowProc,m.nHWnd, ;
m.nMsg, m.wParam, m.lParam)
ENDPROC
PROCEDURE Destroy
UNBINDEVENTS(THIS)
ENDPROC
ENDDEFINE
Listing 1– WM_Test.PRG demonstrates a very simple binding to WM_ACTIVATEAPP, so you can detect when the user leaves/returns to your VFP application.
DO WM_Test at the Command Window, whereupon the global goWM_Test object is instantiated. Its Init event method establishes the BINDEVENT() to the WM_ACTIVATEAPP notification received by the main VFP window, _VFP.hWnd.
Until you UNBINDEVENTS() or release the goWM_Test object, whenever you activate another Windows application, VFP receives the Windows message notification, and in turn calls the delegate HandleEvent method.
Parameters received by the delegate method
The delegate method (your VFP code) always receives the four parameters you see in the HandleEvent method code in listing 1. These parameters are passed to each notified window:
- nHWnd – The Windows ID/handle to the window receiving the notification. You won't do much with this parameter besides passing it along in callbacks. This is the same value as the first BINDEVENT() parameter.
- nMsg – The Windows message notification integer value. This is the same value as the second BINDEVENT() parameter.
- wParam – An integer whose value may or may not be significant, indicating some additional information about the message. For example, the WM_DISPLAYCHANGE notification passes 32 for a screen resolution change, and 16 for a color quality change. The WM_ACTIVATEAPP notification passes 1 to indicate activation, 0 to indicate deactivation.
- lParam – An integer whose value may or may not be significant, indicating some additional information about the message. lParam is meaningful far less often than wParam. The only usage in the example files is in the HandleEvent of the WM_User_SHNotify class in WM_EventHandlers.VCX
CallWindowProc()
The CallWindowProc() API call you see at the end of the HandleEvent method is the recommended way to terminate the delegate method in most scenarios. The CallWindowProc() is semi-analogous to issuing a DODEFAULT() callback in native VFP code, ensuring that the default Windows behavior is indeed executed.
In most cases, you will not be attempting to prevent the native behavior, but simply firing some code in response to an event like the user heading off to another application. It is possible to prevent the native Windows behavior, as demonstrated in some of the code examples near the end of this paper. However, in scenarios like this WM_ACTIVATEAPP example, the standard Windows behavior would NOT be to prevent the user from leaving your VFP app; the HandleEvent delegate method simply allows you to take some action at that point in time.
Bind to hWnd+nMsg –OR- _Screen?
There are a number of WM_* notifications that are the same or the equivalent of _Screen events that you can trap via BINDEVENT(_Screen,"Event",…). I highly recommend using the latter whenever possible – I find it easier/simpler when you stay in VFP. And BINDEVENT()ing to a VFP object has one other major advantage – you can bind one object like _Screen to multiple delegates. Another advantage is that when you bind to a VFP object, you can control when VFP sends the message to the delegate by passing either 1 or 0 as the optional 5th parameter to BINDEVENT().
BINDEVENT()ing to a VFP object has one other major advantage – you can bind one object like _Screen to multiple delegates
Table 1 lists some of the WM_* notifications I found that have a native VFP equivalent. For example, to fire a delegate method to react to the mouse being pressed on the _Screen surface, you can do the whole BINDEVENT(_Screen.hWnd,WM_NCLButtonDown,…) thing, including the CallWindowProc() API call and the 2 or 3 additional DECLARE DLLs, or you can just BINDEVENT(_Screen,"MouseDown",DelegateObject,"DelegateMethod",1).
I couldn't find a WM_* notification equivalent to _Screen.Click. To use Windows message notification for this, you would have to trap for a WM_NCLBUTTONDOWN and a subsequent WM_NCLBUTTONUP, whereas the all-VFP solution is as simple as BINDEVENT(_Screen,"Click",DelegateObject,"DelegateMethod",1)
| WM_* notification message |
Equivalent _Screen event |
| WM_GETMINMAXINFO |
Resize, Moved |
| WM_WINDOWPOSCHANGING |
Resize, Moved |
| WM_WINDOWPOSCHANGED |
Resize, Moved |
| WM_MOUSEMOVE |
MouseMove |
| WM_LBUTTONDOWN |
MouseDown |
| WM_LBUTTONUP |
MouseUp |
| WM_LBUTTONDOWN + WM_LBUTTONUP |
Click> |
| WM_LBUTTONDBLCLK |
DblClick (and Click) |
| WM_RBUTTONDOWN + WM_RBUTTONUP |
RightClick |
| WM_MBUTTONDOWN + WM_MBUTTONUP |
MiddleClick |
Table 1 – Some WM_* notifications that have an all-VFP equivalent, which is always easier to implement.
Limitation: Bind to only one hWnd+nMsg pair
The BINDEVENT() help topic contains this warning near the end of the Remarks section:
" When binding to Windows message (Win Msg) events, only one hWnd to Windows message pairing can exist."
This limitation is significant, especially if you have already been using BINDEVENT() to bind native VFP objects. You can bind any number of delegates to a VFP object property or method, like this:
BINDEVENT(_Screen,"Resize",MyObj1,"OnScreenResize",1)
BINDEVENT(_Screen,"Resize",MyObj2,"OnScreenResize",1)
…and so on…
When the source event occurs or source property is assigned, VFP sends the message to each delegate method.
However, when binding Windows message notifications, you can only bind each hWnd+nMsg pair once. Code like this runs, but each BINDEVENT() overwrites it predecessor, and only the last BINDEVENT() is in effect:
BINDEVENT(_VFP.hWnd,WM_ACTIVATE,MyObj1,"OnAppActivate")
BINDEVENT(_VFP.hWnd,WM_ACTIVATE,MyObj2,"OnAppActivate")
This limitation makes it a challenge when more than one of your VFP objects need to know that a notification has been received. The approach I've taken is to create a single public delegate object, and have it pass the message on to other objects that may need to know about it. For example, here's a snippet of code from the WM_DisplayChange::OnDisplayResolutionChange method:
LOCAL loForm
FOR EACH loForm IN _Screen.Forms
IF PEMSTATUS(m.loForm,"OnThemeChanged",5)
loForm.OnThemeChanged()
ENDIF
ENDFOR
It is relatively easy to pass the message on to any active forms, but as you can see from the code, each active form must have the specified custom method.
While do-able, this technique of creating one "primary" delegate object and making it responsible for passing the message on to "secondary" objects (the primary object may not take any action at all other than passing it on) has definite limitations, including:
- The primary delegate has to "know" each secondary delegate, and the method of the secondary delegate to call
- If the secondary delegate's name and/or delegate method changes, the calls to it must be updated accordingly
In other words, this technique is not as encapsulated as you would like it to be.
One alternative is to handle the message-passing the other way around. Setup the primary object normally. Then the secondary object(s) can BINDEVENT() the primary object method with BINDEVENT(PrimaryDelegate,"Method",THIS,"DelegateMethod",1).
This is a bit of an improvement, but still not as encapsulated as I'd like:
- The primary delegate must exist before any secondary delegates
- The primary delegate must either be a PUBLIC variable reference or known by the secondary delegates by an explicit Name reference
SendMessage() API function
The SendMessage() API function can be helpful for testing your Windows message notifications. SendMessage() is similar to the VFP RAISEEVENT() function. SendMessage() sends a specific nMsg to a specific nHWnd, along with wParam and lParam parameters. Sounds familiar?
Here's an example of how you can use SendMessage() when testing your WM_* notification bindings. Enter and execute each of the lines below in the Command Window. Note that when you send the (first) deactivate message, the VFP window and therefore the Command Window are inactive; you will have to click into the Command Window and enter the (second) activate message without the benefit of the blinking cursor. As each SendMessage() is executed, the bound delegate method executes.
DO WM_ActivateApp
DECLARE INTEGER SendMessage IN WIN32API ;
Integer, Integer, Integer, Integer
WM_ACTIVATEAPP = 0x001C
SendMessage(_VFP.hWnd,WM_ACTIVATEAPP,0,0) &&& deactivate
SendMessage(_VFP.hWnd,WM_ACTIVATEAPP,1,0) &&& activate
More information
For similar-but-different information on Windows message notifications, see my article in the November 2004 issue of FoxPro Advisor Magazine.
Dit artikel is een samenvatting van een uitgebreider “white paper” vol met grotere code samples. Het gehele artikel met de bijbehorende source code kunt u binnenkort downloaden van de SDN-website.