Working With Events In Visual FoxPro 9
Overview
In VFP 9, the Fox team at Microsoft has implemented far more extensibility into both the VFP IDE and the runtime environment than ever before.
I've identified six different events/hooks/techniques that are new to VFP 9. Any one of these features makes a good topic for a presentation or white paper. I'm going to briefly describe four of them, and then concentrate on two of them in particular:
- MENUHIT and MENUCONTEXT IntelliSense scripts
- BINDEVENT() to Windows message notifications
As of this writing, VFP 9 is in beta, a few months from release, so while Microsoft says the feature set is complete, things may change to some extent between now and when you read this paper and/or VFP 9 is released.
Not only is VFP 9 in beta, but the technology I discuss in this paper is brand-new. I will undoubtedly update this paper between the time I submitted it to the conference organizers and the conference – be sure to download whatever updates are made available by the time of the conference.
I'd like to thank members of the Fox team for their help with several issues I've had to address in compiling this information. Doug Hennig has also been a great source of help. And the Fox team permitted me to include the demo code at the end of this paper – they provided it to the beta testers and were gracious enough to let me include it here.
This paper refers to various files containing example code. These examples naturally require VFP 9 or higher to run:
- WM_Test.PRG
- WM_Activate.PRG
- WM_ActivateApp.PRG
- WM_NCActivate.PRG
- WM_User_SHNotify.PRG
- WM_LoopTest.PRG
- BindScreen.PRG
- FoxCode_MenuScripts.PRG
- MenuHitHandler.PRG
- MenuContextHandler.PRG
- FileOpenDialog.PRG
- IsDLLDeclared.PRG
- ShellExecute.PRG
- Demo.VCX, .VCT
- WM_EventHandlers.VCX, .VCT
- WM_PowerBroadcast.SCX, .SCT
- WM_ResolutionChanged.SCX, .SCT
- WM_ThemeChanged.SCX, .SCT
- WinUser.H
I wrote the examples using WinXP, and have not tested them using any other Windows operating systems. And I wrote them as what I'm calling them – examples; you may see any number of ways to modify them to turn them into production code. Having said that, these programs from the above list are library routines I use in production on a daily basis:
- IsDLLDeclared.PRG
- ShellExecute.PRG
_MENUDESIGNER
Specify your own (.PRG/.APP0 menu designer in the new _MENUDESIGNER system variable, and VFP loads your application instead of the default/native Menu Designer.
The _MENUDESIGNER app returns .T. to indicate that it has handled the menu designer request, suppressing the native Menu Designer completely. The _MENUDESIGNER app returns .F. to indicate that the native VFP Menu Designer should be invoked. Use this behavior to call the native Menu Designer if components of your menu designer replacement cannot be found, or to perhaps give the user a choice between the two.
VFP passes three parameters to your code:
cFileName
The first parameter contains the file name of the menu to be edited in the designer. Typically an .MNX file, it contains the fully qualified path.
If the menu doesn't exist yet, cFileName may be an empty string. In this case, the Menu Designer is responsible for saving the file name and adding file to current project if invoked via the New button from Project Manager.
There is a behavior difference between following CREATE MENU commands:
- CREATE MENU MyMenu - VFP first brings up New Menu dialog (select Menu or Shortcut) and then goes directly to menu designer with specified filename passed as cFileName. If filename already exists, VFP first prompts user to replace before calling _MENUDESIGNER.
- CREATE MENU - VFP first brings up New Menu dialog (select Menu or Shortcut) and then goes directly to menu designer with internally generated filename passed as cFileName. This is named sequence in order such as following -- Menu1, Menu2,….
The _MENUDESIGNER application should check for this name since it may want to use a different naming scheme (or even prompt user for a different name).
The native VFP menu designer prompts user when closing the designer to save the filename (possibly a different one). This differs from the first scenario above where name is included in the command, in which case the file is automatically saved without invoking File>Save dialog.
- CREATE MENU ? - VFP invokes the File>New dialog first and prompts user to replace if file already exists (this name is passed as cFileName parameter). The New Menu dialog is then displayed before invoking _MENUDESIGNER.
nCommandType
The second parameter contains detail for the command used to invoke the designer:
- Create
- Create Shortcut - only occurs when New Menu dialog is first invoked.
- Modify
aDetail
The third parameter contains an array of detail for the specific menu command. The array has 2 elements per row detailing the specific call.
- If the call was made via simple menu or toolbar, no array is created.
- If the call was made via basic command (e.g., CREATE MENU), no array is created.
- If the call was made via Project Manager (e.g., New button), then the array contains reference to project name.
- If the call was made via command (e.g., CREATE MENU….NOWAIT WINDOW…), then the array is populated with keywords specified.
| Element |
Description |
| PROJECT |
Name of the project, menu designer invoked via the Project Manager |
| NOWAIT |
.T. if this keyword was passed to the CREATE MENU command |
| SAVE |
.T. if this keyword was passed to the CREATE MENU command |
| WINDOW |
Name of the specified window |
| IN |
Name of the specified window, or SCREEN |
Table 1 – aDetail array elements.
If the specified menu does not exist, VFP treats both CREATE MENU and MODIFY MENU the same.
Member Data scripts
The MemberData Editor supports a Script editbox, where you can enter a script in the form of VFP code. The Script typically uses ASELOBJ() to get an object reference to the object(s) currently selected in the Form/Class Designer, presents some sort of custom dialog you write, and updates the property accordingly.
As shown in Figure 1, many native VFP properties already support the property editor ellipsis button. For those (native or custom) properties that you specify a MemberData script, VFP also displays the property editor ellipsis button. When you select the button, VFP executes the script code.

Figure 1 – The mouse is positioned over the Property Editor ellipsis button. This button appears for native VFP properties for which VFP has a property editor, like the ForeColor shown here, whereupon the GETCOLOR() dialog is summoned. However, if you specify a Script in the MemberData Editor for any property, VFP enables this button and executes the code in the script when you select the button.
For more information on MemberData scripts, please see my article "VFP 9 Form and Class Design Enhancements" in the July 2004 issue of FoxPro Advisor magazine.
Report events in the Report Designer
In the Report Designer, you can replace the various native VFP dialogs by trapping events raised in the Designer and substituting your own forms.
Runtime events in reports
The new ReportListener class receives notification as events occur as reports are run, and supports making runtime modifications to the report. The ReportListener is at the heart of the wealth of techniques you can implement in your VFP reports. No doubt you've already heard how easy it is to output HTML/XML, add graphs, rotate labels, etc.
MENUHIT and MENUCONTEXT IntelliSense scripts
VFP 9 includes a relatively simple mechanism that allows you to intercept every selection of an item on the VFP system menu in the IDE, driven from a special record in your FoxCode.DBF/IntelliSense table. An almost identical record allows you to intercept every call to a context-sensitive/shortcut menu in the VFP IDE. Once intercepted, your VFP code can either replace or augment the native behavior.
For similar-but-different information on MENUHIT and MENUCONTEXT, see my article on the subject in the October 2004 issue of FoxPro Advisor Magazine.
MENUHIT
In VFP 9, if your IntelliSense table contains a "MENUHIT" record, VFP 9 automatically executes whatever script is in the Data field for that record every time you make a selection from the VFP menu. It's as simple as that.
Well, almost. Additionally, if your script sets the ValueType property of the received toParameter parameter object to "V" or "L", then VFP does not continue with the default/native behavior for that menu selection – your script is responsible for handling that menu request. The value RETURNed by your script is ignored; it's the toParameter.ValueType setting that determines whether the native VFP action is suppressed.
Critically important is the fact that VFP passes a parameter object to your IntelliSense script, which contains these two property values:
- toParameter.UserTyped - The system menu item that was just selected
- toParameter.MenuItem - The name of the menu item that was just selected
The MENUHIT FoxCode.DBF/IntelliSense record
VFP 9 doesn't ship with a "MENUHIT" record in the default FoxCode.DBF/IntelliSense table. You can, of course, add your own record with Type="S", Abbrev="MENUHIT" and write your own script to the Data field. The Solution Samples for VFP 9 include a MENUHIT example, which adds a "MENUHIT" record with this default Data (script) code:
LPARAMETERS toParameter
LOCAL lnSelect, lcCode, llReturn, lScriptHandled, lcSaveUdfParms
TRY
* First try FoxCode lookup for Type="M" records
lnSelect = SELECT()
SELECT 0
USE (_FOXCODE) AGAIN SHARE ORDER 1
IF SEEK('M' + PADR(UPPER(toParameter.MenuItem), LEN(ABBREV)))
lcCode = DATA
ENDIF
USE
SELECT (lnSelect)
IF NOT EMPTY(lcCode)
llReturn = EXECSCRIPT(lcCode, toParameter)
lScriptHandled=.T.
ENDIF
* Handle by passing to external routine as specified in Tip field
IF !lScriptHandled
lcProgram = ALLTRIM(toParameter.Tip)
IF FILE(lcProgram)
lcSaveUdfParms=SET("Udfparms")
SET UDFPARMS TO REFERENCE
DO (lcProgram) WITH toParameter,llReturn
SET UDFPARMS TO &lcSaveUdfParms
ENDIF
ENDIF
* Custom script successful so let's disable native behavior
IF llReturn
toParameter.ValueType = 'V'
ENDIF
CATCH
ENDTRY
RETURN llReturn
Listing 1 – The script (VFP code) in the Data field of the "MENUHIT" FoxCode.DBF record.
The script has four sections:
- The LPARAMETERS statement. All IntelliSense scripts are passed a toParameter object by the VFP IntelliSense engine. The VFP online Help contains explanations of the IntelliSense engine and the toParameter object. Here, we're interested in the toParameter.UserTyped property, which is the name/prompt of the system menu pad/bar that was just selected and the toParameter.MenuItem, which is the name/prompt of the item that was just selected.
- The FoxCode lookup for "M" scripts. This technique supports creating one IntelliSense record for each menu selection you want to replace or augment. Just set the Abbrev field to "M" plus the toParameter.MenuItem string. That record is located and its Data field script executed.
- If no "M" script is found for the toParameter.MenuItem, the Tip field is checked for a .PRG/.APP to run instead of an IntelliSense script. If the indicated .PRG/.APP exists, it is called. This is an alternative to adding one IntelliSense per menu selection you want to replace or augment. I prefer this technique because it is much easier to debug a separate .PRG than an IntelliSense script, and because you only need the one IntelliSense record, making it easier to maintain.
- If a menu hit script was executed and handled the action, the loParameter.ValueTyped is set to "V" which, along with RETURNing .T. suppresses the native VFP behavior for that menu selection.
I've included an example program named FoxCode_MenuScripts.PRG that you can use as-is or customize as you see fit. When you DO FoxCode_MenuScripts.PRG, a "MENUHIT" record is added if it doesn't already exist. If one does exist, its Data field is updated with the default script code listed above, and its Tip field is updated with "MenuHitHandler.PRG". You can run FoxCode_MenuScripts whenever and as many times as you want.
The script is just a "mini VFP program" that is executed internally via EXECSCRIPT() whenever you make a menu selection. The script finds no "M"-specific IntelliSense records, and delegates the work to the program named MenuHitHandler.PRG, if it exists; the interesting code is in MenuHitHandler.PRG.
I've included MenuHitHandler.PRG in the example files; feel free to modify as you see fit. The Data field script can get very sophisticated, but given how much of a pain it is to debug IntelliSense scripts, I prefer this approach, where the script calls an external .PRG where I can easily SET STEP ON, etc. It also makes it easier to maintain the FoxCode.DBF, because there is only this one "MENUHIT" record to update, and it is very simple. The other approach has this "MENUHIT" script look for other FoxCode.DBF records, each record containing a different script for each handled menu selection. I would rather deal with one separate .PRG and perhaps .PRGs it calls than FoxCode.DBF scripts.
Viewing the script in the Data field of your FoxCode.DBF "MENUHIT" record is easy:
USE (_FoxCode) AGAIN IN 0 SHARED ALIAS Temp
BROWSE LAST FOR UPPER(Abbrev) = "MENUHIT"
USE IN Temp

Figure 2 – The Data field (script) in the MENUHIT FoxCode.DBF/IntelliSense table.
Once this MENUHIT record exists, its script is executed every time you make a menu selection from the VFP system menu (in the development environment). If your FoxCode.DBF table doesn't have a MENUHIT record, VFP does what it always did in the past, and executes the native/default behavior for the selected menu item. If the script generates an error, it is completely ignored (you don't see any error messaging), and VFP executes the native/default behavior for the selected menu item; hence the value of keeping the script extremely simple.
Near the top of the MenuHitHandler.PRG included with the examples, you'll find a line of code that is *!* commented; you can un-comment it to see the toParameter.UserTyped and toParameter.MenuItem values as you make VFP system menu selections.
Replace the New Property…/New Method… dialogs
OK, so now your MenuHitHandler.PRG is called every time you make a selection from the VFP system menu. So what?
Well, now you can intercept those menu hits and replace them when you want. Why/when would you want to? Any time the default VFP action irritates you and you think you can do a better job.
That's just what VFP developer Doug Hennig did. When he found out he could intercept system menu selections, he wrote a replacement for the New Property…/New Method… dialogs you get on the Form/Class menus in the Form/Class Designers. He finds these behaviors irritating:
- The dialog is modal, preventing you from moving back and forth to the Designer and/or Properties Sheet to check things out.
- The dialog is not resizable.
- To add a single property or method, you have to click both the Add and Close buttons.
- Starting in VFP 9 (which he's been beta testing for over a year now), you can add PEMs to the new Favorites tab of the Properties Sheet. You will frequently want to do that at the time you add a custom property or method, but the native dialogs don't support that feature.
- Starting in VFP 9, you can specify the way the property/method name is displayed in the Properties Sheet, IntelliSense, etc. Again, it only makes sense to set that display string when you add the property/method, but again the native dialogs don't support doing that.
So Doug wrote his own replacement for the New Property…/New Method… dialogs. He has graciously made the code available to you and me in the NewPropertyDialog.ZIP/NewPropertyDialog.APP files I've included with the examples. PLEASE NOTE that while this code works pretty well, Doug will likely be enhancing and updating it up until the time VFP 9 ships. The final version of NewPropertyDialog.APP will ship with VFP 9, but by default VFP fires the native dialogs; if you want this replacement dialog, you'll have to hook it in yourself, using a MENUHIT IntelliSense script like this one. In the meantime, I thank Doug for making this one freely available.
My MenuHitHandler.PRG is quite simple. It receives the toParameter object parameter that was received from VFP by the MENUHIT script and passed on to MenuHitHandler.PRG. Then it has a DO CASE..ENDCASE for processing specific menu hits. The first CASE looks like this, for processing New Property… and New Method…:
********************************************************
CASE UPPER(toParameter.MenuItem) = "NEW PROPERTY..." ;
OR ;
UPPER(toParameter.MenuItem) = "NEW METHOD..."
********************************************************
LOCAL llMethod, llClass, lcNPDFile, lcVCX
lcVCX = "NewProperty.VCX"
*
* try the VFP installation folder first
*
lcNPDFile = ADDBS(HOME()) + "NewPropertyDialog.APP"
DO CASE
CASE FILE(m.lcNPDFile)
* found it in the VFP installation folder
CASE FILE("NewPropertyDialog.APP")
* found it somewhere in the current VFP path
lcNPDFile = FULLPATH("NewPropertyDialog.APP")
OTHERWISE
* can't find it
m.lcNPDFile = SPACE(0)
ENDCASE
IF NOT EMPTY(m.lcNPDFile)
TRY
llMethod = UPPER(toParameter.MenuItem) = UPPER("New Method")
llClass = UPPER(toParameter.UserTyped) = UPPER("Class")
RELEASE _oNewProperty
PUBLIC _oNewProperty
_oNewProperty = NEWOBJECT("NewPropertyDialog", ;
m.lcVCX, ;
m.lcNPDFile, ;
m.llMethod, ;
m.llClass)
_oNewProperty.Show() &&& modeless form
CATCH TO loException
ENDTRY
ENDIF
IF VARTYPE(m.loException) = "O" OR EMPTY(m.lcNPDFile)
llRetVal = .f. &&& VFP handles this menu hit normally
* to help you with debugging, output this to _Screen
? loException.Message
ELSE
llRetVal = .t. &&& menu hit handled, suppress native VFP behavior
ENDIF
Listing 2 – The section of MenuHitHandler.PRG that processes New Property… and New Method… menu selections.
This code is quite simple:
- Instantiate the NewPropertyDialog form class in NewPropertyDialog.APP
- Tell VFP not to bother processing the menu hit normally
Simple is beautiful, and as you can see, this all just VFP code you already know how to write. The part we couldn't do before was get that MENUHIT script called automatically/internally by VFP on each menu selection.
Here's what the replacement New Property… dialog looks like. It's quite similar to the native one, but eliminates the deficiencies noted above.

Figure 3 – Doug Hennig's New Property… dialog replaces the native one.
Replace the File>Open dialog
What? Replace the File>Open dialog? Why?
Because it's a "dumb" GetFile() Windows dialog, and I hate the way it works in the VFP development environment. For example:
- By default, File>Open thinks I want to open a .PJX file. That's rarely true for me, so I'd prefer it default to showing all files.
- If I select a file with the extension PJT, SCT, VCT, FRT, FXP, etc., File>Open opens the selected file in a MODIFY FILE window. I'd like File>Open to be smart enough to realize that I've simply selected the wrong file, and open the corresponding PJX, SCX, VCX, FRX, PRG, etc. in the corresponding editor.
- If I select a CDX or FPT, File>Open opens the file in a MODIFY FILE window. I'd prefer to either BROWSE or MODIFY STRUCTURE the owning table.
- If I select an HTM/HTML/XML file, File>Open opens the selected file in a MODIFY FILE window. I usually prefer to see those files in Internet Explorer.
- If I select a graphic file, a Word document, Excel spreadsheet, etc., File>Open opens them in a MODIFY FILE window. I suppose I might have wanted that behavior once or twice in my programming career, but I can't remember the last time. I'd like instead to see those files opened in the editor I've specified in Windows as the associated application/editor.
So I wrote my own File>Open replacement utility a long time ago. I've included FileOpenDialog.PRG with the example files.
Before VFP 9, I had to call FileOpenDialog() from the Command Window or my own custom menu option or hotkey. Now, in VFP 9, I have just added this CASE to MenuHitHandler.PRG:
********************************************************
CASE UPPER(ALLTRIM(toParameter.UserTyped)) == "FILE" ;
AND UPPER(ALLTRIM(toParameter.MenuItem)) == "OPEN..."
********************************************************
*
* this CASE intercepts the File>Open menu selection,
* calling FileOpenDialog() instead
* MODIFY COMMAND FileOpenDialog
*
IF FILE("FileOpenDialog.PRG")
FileOpenDialog() &&& run the replacement
llRetVal = .t. &&& suppress the native File>Open
ENDIF
Three lines of actual code (plus the IF FILE() check)! Now that's powerful! My replacement for the native File>Open menu selection suits my needs, but better yet, can be customized any time I want to tweak it. For example, I've thought about popping up a dialog when I select an HTM, HTML, or XML file, asking me if I'd prefer a MODIFY FILE window or the editor I've associated with those files (Internet Explorer).

Figure 4 – If I inadvertently select this .SCT rather than its corresponding .SCX, the native Open dialog from the File menu opens this .SCT in a MODIFY MEMO window – with my replacement FileOpenDialog() behavior, the corresponding .SCX form is opened in the Form Designer.
Tinkering with MenuHitHandler.PRG
MenuHitHandler.PRG contains this code near the beginning:
*
* un-comment this line to see these 2 values echoed
* to the _Screen:
*!* ? toParameter.UserTyped, toParameter.MenuItem
If you're still having trouble grasping how the MENUHIT script works, you might try uncommenting the indicated line. From then on, until you comment it out again, you'll see these two values output to _Screen every time you make a VFP menu selection. Even for actions like Cut, Copy, and Paste via {CTRL+X}, {CTRL+C}, and {CTRL+V} hotkeys, because those hotkeys are just menu shortcuts, resulting in a menu hit.
Custom menu pad and bar selections fire the MENUHIT script
The MENUHIT script is even called for pads and their menu bars that you add programmatically to _MSYSMENU – literally every menu hit on the VFP system menu triggers that event. Now, I'm not sure I will ever have a use for intercepting hits on my own custom pads and bars I add to the system menu, but it's great that VFP is consistent.
Where do you want to go today?
I hope this explanation and these two examples get you excited about the prospects of what you can do with the MENUHIT script.
To intercept more menu selections, just add your own CASEs to MenuHitHandler.PRG or your own menu hit handler.
MENUCONTEXT
The "MENUCONTEXT" IntelliSense record works the same way as the "MENUHIT" record, and its script (contents of the Data field) is called automatically by VFP whenever you request a shortcut/context-sensitive menu.
The FoxCode_MenuScripts.PRG in the example files adds/updates the "MENUCONTEXT" record in your FoxCode.DBF/IntelliSense table, same as it does the "MENUHIT" record. The script is identical to the "MENUHIT" script in Listing 1 above. The only difference is that the Tip field is set to the external MenuContextHandler.PRG program, so all system shortcut menu hits are sent through MenuContextHandler.PRG.
MenuContextHandler.PRG works the same way MenuHitHandler.PRG does, although as of this writing I have not come up with any cool MenuHitContext.PRG examples.
There are a few differences between MENUCONTEXT and MENUHIT. First, the script receives a comparable object parameter, but the significant property values are a bit different.
toParameter.MenuItem is the internal Visual FoxPro menu ID (integer) for the shortcut menu. As of this writing, there is nowhere you can go to lookup these values so you an intercept a specific shortcut menu; it's doubtful Microsoft will document these anywhere in the VFP 9 online help file. You can, however, easily determine the values by un-commenting this code near the beginning of MenuContextHandler.PRG to see what happens whenever you request a shortcut menu in the VFP IDE:
*
* un-comment these lines to see these values echoed
* to the _Screen:
*!* ? toParameter.MenuItem
*!* LOCAL xx
*!* FOR xx = 1 TO ALEN(toParameter.Items,1)
*!* ? toParameter.Items[m.xx]
*!* ENDFOR
As you have no doubt surmised from the above code, the toParameter.Items property is an array of strings containing the text for each shortcut menu prompt.
MENUCONTEXT promises to be a lot less useful than MENUHIT because:
- There is no way to replace individual items; it's an all-or-nothing proposition, and you must code an entire replacement shortcut menu.
- There is no way to code the action for any of the native menu items, so that you could replace just one of the options and pass the others on to VFP or execute the same behavior it would have. For example, there is no way to replace just the Run Form option on the shortcut menu for the Form Designer/form surface. You have to replace the entire shortcut menu with one of your own, which means you have to code the other behaviors, too; for many VFP IDE shortcut menus, there is no way to code the native behaviors yourself.
So, as I mentioned above, I don't have any MENUCONTEXT examples. I'll likely come up with something in the future, so stay tuned. And be sure to download the latest version of this document and example code by the time of the conference.
BINDEVENT() basics
VFP 8 introduced the BINDEVENT() function, accompanied by UNBINDEVENTS() and AEVENTS(). This paper assumes that you have a basic understanding of how BINDEVENT() works in VFP 8. If not, you can always review the many BINDEVENT() articles I've written in FoxPro Advisor Magazine, starting with the "New BINDEVENT() Function in VFP 8 Has Many Uses" article in the December 2003 issue.
You need to understand BINDEVENT() fundamentals in order to comprehend/appreciate the enhancements Microsoft made to VFP 9 to support binding to Windows message notifications.
BindScreen.PRG example
However, I've included a BindScreen.PRG program in the example files, to demonstrate BINDEVENT() basics here. You can review the code to see how simple it is to bind _Screen and _VFP properties, events, and methods (PEMs) to a delegate method. When the property value is assigned or the method fires, VFP executes the delegate method.
DO BindScreen, and virtually all the PEMs of the _Screen and _VFP objects are bound to a delegate method. As properties are assigned and events triggered, _Screen output lets you know when the delegate fires. BindScreen.PRG changes properties and takes actions that fire the delegate for a small subset of the total bound PEMs (see Figure 5), and you can experiment from there.

Figure 5 – BindScreen.PRG not only establishes BINDEVENT()s for most _Screen and _VFP PEMs, it also takes several actions that are logged to _Screen output.
To clean up the event bindings BindScreen sets up, execute these commands, as shown in Figure 5:
CLEAR ALL
CLOSE ALL
UNBINDEVENTS(0)
MODIFY WINDOW SCREEN
Unfortunately, not all the _Screen methods listed by IntelliSense and AMEMBERS() fire the delegate method. So a utility like BindScreen.PRG is valuable for testing any particular method you plan to bind.
I selected _Screen and _VFP as examples for two reasons. First, it's easy to understand how valuable BINDEVENT() is for these two VFP objects, because you can't subclass them and add code to various methods or add Assign methods – BINDEVENT() allows you to hook code directly to these events. Second, the new Windows message notifications behaviors added to VFP 9 are typically associated with _Screen or _VFP.
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. More on that later.
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 in 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 WinUser.H in my \Program Files\Microsoft Visual Studio .NET\ VC7\PlatformSDK\Include folder. I have also included a WinUser.H file in the example files. WinUser.H contains a lot of defined constants; the Windows message subset of constants typically start with the prefix "WM_", so you can do a text search on "WM_" to find them. See Figure 6.

Figure 6 – The WinUser.H header file specifies a large number of constants, including many WM_ Windows message values.
For example, you can pass the WM_ACTIVATEAPP constant 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 listing 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.
I'm hoping the good folks at Microsoft will update the BINDEVENT() help topic to include a list of the WM_* constants in the WinUser.H file that we can expect to successfully bind to in our VFP applications. I have nowhere near tested them all, but I know that some events are easily trapped in VFP, while others don't apparently send any notification to VFP at all.
VFP documentation will likely continue to be sparse on this entire Windows message notification subject. There is a lot of information available in MSDN and other Internet sources, but you have to slog through a lot of different pages and, obviously, non-VFP code examples. Try doing a Yahoo/Google search on something like WM_ACTIVATE, and you'll see what I mean.
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 to beta testers say "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. This WM_Test.PRG is included in the example files.
* WM_Test.PRG
CLEAR ALL
CLOSE ALL
CLEAR
UNBINDEVENTS(0)
PUBLIC goWM_Test
goWM_Test = CREATEOBJECT("cusWM_Test")
RETURN
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 2 – 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 you 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. In this simple example, I've output text to _Screen so you can see that the HandleEvent method is firing, and whether it fires in response to the user activating another Windows app or returning to the VFP session.
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 2. 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-analagous 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.
WM_EventHandlers.VCX/WM_EventHandlers
This class library in the example files contains one abstract class and several concrete subclasses that demonstrate some fairly simple Windows message BINDEVENT() examples.
WM_EventHandlers is an abstract class that provides inheritance to the other concrete classes you see in the WM_EventHandlers.VCX, as seen in Figure 7. In addition to abstracting several DECLARE DLLs used by all Windows message notification implementations, WM_EventHandlers establishes the HandleEvent method, which I've used as the delegate for all the examples.

Figure 7 – WM_EventHandlers.VCX contains one abstract class and several concrete subclasses.
All VFP examples I've seen so far use .PRG-based classes, partly because they are completely self-contained and do everything from scratch. You will see a lot of code repeated over and over again using this approach.
Instead, I have created a 2-level class hierarchy, so that the tasks that are repeated in every implementation are inherited rather than re-coded. And I've used visual classes (inheriting from the VFP base class Custom), because I find them so much easier to work with. For example, when I want to see what is inherited by the Init or HandleEvent methods, I find it much easier to click the View Parent Code button in the method snippet editor than to search for the .PRG containing the parent class, MODIFY COMMAND it, and find the method.
I also designed the WM_* classes to handle one Windows message notification each, as you can see from their names (see Figure 7). So far, I like this approach primarily because you can only bind one hWnd+nMessage pair to one delegate method.
WM_EventHandlers.VCX/WM_Activate, WM_Activate.PRG
As its name implies, the WM_Activate class binds to the WM_ACTIVATE Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_Activate OF WM_EventHandlers METHOD Init
Whenever the main VFP window is activated or deactivated, VFP receives the Windows notification and calls the delegate method, WM_Activate::HandleEvent():
MODIFY CLASS WM_Activate OF WM_EventHandlers METHOD HandleEvent
WM_Activate::HandleEvent() further delegates the action by determining whether the WM_ACTIVATE message was received because the main VFP window was activated or deactivated, and calling the corresponding method, where you can code specific behaviors. The OnWindowActivate/OnWindowDeactivate methods contain simple code to output text to _Screen, to demonstrate its firing. You can take over from there, to code whatever behaviors you want.
The WM_Activate.PRG example program demonstrates binding to WM_ACTIVATE via an instance of the WM_Activate class. DO WM_Activate, and each time you leave/return to the VFP session (such as via {ALT+TAB}, clicking on an app in the Task Bar, etc.), you see _Screen output documenting that action, as seen in Figure 8.

Figure 8 – WM_Activate.PRG in action. As you leave/return to the VFP session, the delegate method fires and outputs to _Screen.
WM_ACTIVATE holds the promise of finally allowing us VFP developers to trap when the user leaves or returns to our VFP applications, whereupon we can code whatever is appropriate.
WM_ACTIVATE anomaly
However, as of this writing, there is one major flaw with binding to WM_ACTIVATE. If you activate the VFP application by clicking on its TitleBar, VFP processes the single click as if it was a double-click:
- If the VFP window is currently normalized, activating it by clicking its TitleBar maximizes it
- If the VFP window is currently maximized, activating it by clicking its TitelBar normalizes it
I consider this behavior to be a VFP bug, because it clearly doesn't happen with other Windows applications. I have reported it to the Fox team as such, but so far the response is that you have to work around it. The WM_Activate class in WM_EventHandlers.VCX contains such a workaround, consisting of a custom flag property, a custom method, and code in the Init and HandleEvent methods. See the comments in the Init for a full explanation.
WM_ACTIVATE delegate fires too often
There is another problem with binding to WM_ACTIVATE. The delegate fires not only when moving to/from the main VFP application window, but also when moving between child IDE windows within VFP.
Activating windows like the Properties Sheet, Designer windows, even clicking on the _Screen surface from somewhere like the Command Window fire the delegate method. WM_ACTIVATE even fires (twice) on QUITting VFP. Those events may be something you want to trap in the development environment, for some sort of developer tools, but when testing, I found that they simply get in the way.
Unfortunately, WM_ACTIVATE bindings are commonly listed everywhere you look for examples (such as MDSN). I have found WM_ACTIVATEAPP better because it only fires the delegate when the user leaves/returns to the main VFP window, not when moving between child windows.
WM_EventHandlers.VCX/WM_ActivateApp, WM_ActivateApp.PRG
As its name implies, the WM_ActivateApp class binds to the WM_ACTIVATEAPP Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_ActivateApp OF WM_EventHandlers METHOD Init
Whenever the main VFP window is activated or deactivated, VFP receives the Windows notification and calls the delegate method, WM_ActivateApp::HandleEvent():
MODIFY CLASS WM_ActivateApp OF WM_EventHandlers METHOD HandleEvent
WM_ActivateApp::HandleEvent() further delegates the action by determining whether the WM_ACTIVATEAPP message was received because the main VFP window was activated or deactivated, and calling the corresponding method, where you can code specific behaviors. The OnWindowActivate/OnWindowDeactivate methods contain simple code to output text to _Screen, to demonstrate its firing. You can take over from there, to code whatever behaviors you want.
The WM_ActivateApp.PRG example program demonstrates binding to WM_ACTIVATEAPP via an instance of the WM_ActivateApp class. DO WM_ActivateApp, and each time you leave/return to the VFP session (such as via {ALT+TAB}, clicking on an app in the Task Bar, etc.), you see _Screen output documenting that action, as seen in Figure 9.

Figure 9 – WM_ActivateApp.PRG in action. As you leave/return to the VFP session, the delegate method fires and outputs to _Screen.
I prefer WM_ACTIVATEAPP to WM_ACTIVATE (discussed in the previous section), because the WM_ACTIVATEAPP notification is only broadcast when you leave/return to the main VFP window, not when moving around in its child windows.
However, WM_ACTIVATEAPP suffers from the same significant anomaly as WM_ACTIVATE, where activating the main VFP window by clicking on its TitleBar is processed like a double-click. See the WM_ACTIVATE anomaly sub-section in the previous WM_ACTIVATE section for a full discussion. The WM_ActivateApp class in WM_EventHandlers.VCX contains the same workaround as the WM_Activate class.
Another option is the WM_NCACTIVATE message. WM_NCACTIVATE fires when the non-client part of the window is activated. Unfortunately, it, too, suffers from this click-the-TitleBar anomaly, and the WM_NCActivate class contains the same workaround.
Don't message the user
When you bind to WM_ACTIVATEAPP (or WM_ACTIVATE/WM_NCACTIVATE, for the same reason), don't message the user via a MESSAGEBOX() or your own VFP form. If you do, the delegate method fires again, and strange things can happen when Windows thinks it's time to leave your VFP application but you interrupt the normal process with some sort of dialog.
I had already come to this conclusion on my own, but when I was surfing the Internet looking for WM_ACTIVATE/WM_ACTIVATEAPP documentation, I read the same warning KB article 64329: http://support.microsoft.com/default.aspx?scid=kb;en-us;64329.
As with most Windows message notifications, you simply have the opportunity to execute some code when the notification is received. This is the same philosophy you should have when implementing BINDEVENT()s to VFP objects – the delegate code just fires in addition to whatever action triggered it.
WM_EventHandlers.VCX/WM_NCActivate, WM_NCActivate.PRG
As its name implies, the WM_NCActivate class binds to the WM_NCACTIVATE Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_NCActivate OF WM_EventHandlers METHOD Init
Whenever the main VFP window is activated or deactivated, VFP receives the Windows notification and calls the delegate method, WM_NCActivate::HandleEvent():
MODIFY CLASS WM_NCActivate OF WM_EventHandlers METHOD HandleEvent
WM_NCACTIVATE is yet another alternative to WM_ACTIVATE; I've found WM_NCACTIVATE to behave about the same as WM_ACTIVATEAPP.
WM_EventHandlers.VCX/WM_DisplayChange, and WM_ResolutionChanged.SCX
As its name implies, the WM_DisplayChange class binds to the WM_DISPLAYCHANGE Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_DisplayChange OF WM_EventHandlers METHOD Init
Whenever the user changes the Screen Resolution or Color Quality in the Settings tab of the Display Properties dialog (RightClick on the Windows desktop, select the Properties… option), VFP receives the Windows notification and calls the delegate method, WM_DisplayChange::HandleEvent():
MODIFY CLASS WM_DisplayChange OF WM_EventHandlers METHOD HandleEvent
WM_DisplayChange::HandleEvent() further delegates the action by determining whether the WM_DisplayChange message was received because of a Screen Resolution change or a Color Quality change, and calling the corresponding method, where you can code specific behaviors. The OnDisplayResolutionChange method contains code to pass the message on to all active VFP forms that have a custom OnResolutionChanged method.
The WM_ResolutionChanged.SCX example form demonstrates binding to WM_DISPLAYCHANGE via an instance of the WM_DisplayChange class. DO FORM WM_ResolutionChanged, and each time you change the screen resolution, the delegate method resizes and repositions the main VFP window accordingly, and then passes the message on to the form so it can do the same.
See the custom zReadMe documentation of WM_ResolutionChanged.SCX for more details.

Figure 10 – WM_ResolutionChanged.SCX receives notification from the WM_DisplayChange instance when the user changes the screen resolution. The Info… option is available from the shortcut menu for each of the WM_*.SCX example forms.
The zReadMe details are also available while any of the WM_*.SCX example forms are running, via the Info… shortcut menu option, as shown in Figure 10.
WM_EventHandlers.VCX/WM_PowerBroadcast, and WM_PowerBroadcast.SCX
As its name implies, the WM_PowerBroadcast class binds to the WM_POWERBROADCAST Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_PowerBroadcast OF WM_EventHandlers METHOD Init
Whenever any of a number of system events that affect power to the computer occur, VFP receives the Windows notification and calls the delegate method, WM_PowerBroadcast::HandleEvent():
MODIFY CLASS WM_PowerBroadcast OF WM_EventHandlers METHOD HandleEvent
WM_PowerBroadcast::HandleEvent() shows how to determine exactly which power-related event has been broadcast by checking the wParam value, and calling the corresponding custom method. The OnPowerStatusChange custom method alerts the (notebook computer) user that their power cable may have been accidentally unplugged.
The WM_PowerBroadcast.SCX example form demonstrates this behavior. DO FORM WM_PowerBroadcast and unplug your laptop computer. The MESSAGEBOX() you see in Figure 11 is generated in the WM_PowerBroadCast::OnPowerStatusChange method:
MODIFY CLASS WM_PowerBroadcast OF WM_EventHandlers METHOD OnPowerStatusChange

Figure 11 – WM_PowerBroadcast.SCX makes it easy to see the WM_POWERBROADCAST notification in action.
See the custom zReadMe documentation of WM_PowerBroadcast.SCX for more details.
The zReadMe details are also available while any of the WM_*.SCX example forms are running, via the Info… shortcut menu option, as shown in Figure 10.
There are a LOT of different power change events you can trap for. If you search for "WM_POWERBROADCAST" in WinUser.H, you will see a number of PBT_* constants after the WM_POWERBROADCAST constant. The PBT_* constants correspond to wParam values received by the delegate method, so you can add a DO CASE..ENDCASE to WM_PowerBroadcast::HandleEvent() to have a place to code each possible action.
I have included several other Windows message notification code examples at the end of this paper; one of those examples shows other uses for WM_POWERBROADCAST.
WM_EventHandlers.VCX/WM_ThemeChanged, and WM_ThemeChanged.SCX
As its name implies, the WM_ThemeChanged class binds to the WM_THEMECHANGED Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_ThemeChanged OF WM_EventHandlers METHOD Init
Whenever the user changes attributes for the current Theme, such as font and color attributes in the Display Properties dialog (RightClick on the Windows desktop, select the Properties… option), VFP receives the Windows notification and calls the delegate method, WM_ThemeChanged::HandleEvent():
MODIFY CLASS WM_ThemeChanged OF WM_EventHandlers METHOD HandleEvent
WM_ThemeChanged::HandleEvent() takes no action of its own (it could, of course), but rather passes the message on to all active VFP forms that have a custom OnThemeChanged method.
The WM_ThemeChanged.SCX example form demonstrates binding to WM_ThemeChanged via an instance of the WM_DisplayChange class. DO FORM WM_ThemeChanged, and each time you change something about the current Theme, the WM_ThemeChanged::HandleEvent method notifies the form, which calls its custom SetColors() method to keep the grid.HighlightBackColor and label.ForeColor in synch with any change the user has made.
See the custom zReadMe documentation of WM_ThemeChanged.SCX for more details.

Figure 12 – WM_ThemeChanged.SCX receives notification from the WM_ThemeChanged instance when the user changes anything about the current Theme. The form fires its SetColors method, to update the grid.HighlightBackColor and label.ForeColor. The Info… option has been selected from the form shortcut menu, and the step-by-step instructions in the form::zReadMe are visible.
The zReadMe details are also available while any of the WM_*.SCX example forms are running, via the Info… shortcut menu option, as shown in Figure 10.
WM_EventHandlers.VCX/WM_User_SHNotify, and WM_ThemeChanged.PRG
As its name implies, the WM_User_SHNotify class binds to the WM_USER_SHNOTIFY Windows message notification. The BINDEVENT() is setup in the Init event method:
MODIFY CLASS WM_User_SHNotify OF WM_EventHandlers METHOD Init
Whenever the current user makes any of the disk changes specified in WM_User_SHNotify::Init, VFP receives the Windows notification and calls the delegate method, WM_User_SHNotify::HandleEvent():
MODIFY CLASS WM_User_SHNotify OF WM_EventHandlers METHOD HandleEvent
This class is a bit different from the others in WM_EventHandlers.VCX. The BINDEVENT() is to the single WM_User_SHNotify message notification, but the code in WM_User_SHNotify::Init registers a number of specific SHCNE_* disk events for the current user that cause the HandleEvent delegate method to fire.
When you DO WH_User_SHNotify, you can make changes like renaming, adding, deleting a file/folder in the Windows Explorer, and see the delegate message fire.
Miscellaneous Stuff
Based on my work with binding to Windows message notifications, the most important piece of advice I can pass on is to test everything. Make no assumptions, because each WM_* notification I've experimented with has behaved a bit differently from the rest of them.
I've compiled a number of other items you may find useful.
Spy++
Visual Studio .NET includes a Spy++ tool that is handy for observing what Windows message notifications are received by Windows windows. It is quite helpful for watching the messages VFP receives from Windows and are therefore candidates for BINDEVENT().

Figure 13 – Find the Spy++ utility on the Tools menu for Visual Studio .NET.
The Spy++ utility has a lot of features and options. One common use that applies to this discussion is the Find Window… option on the Spy menu. As shown in Figure 14, follow the instructions to find your main VFP application window. Note that _VFP is the main VFP window, and _Screen and the Command Window are separate child windows; drag the Spy++ Finder Tool cursor over the TitleBar of the main VFP application window to find _VFP.hWnd, and release the Finder Tool. Select the Messages option and the OK button. Then watch the messages fly by as you take various actions in the VFP session, as shown in Figure 15. Compare those messages with the WM_* list in your WinUser.H file.

Figure 14 – The Find Window… selection on the Spy menu summons the Spy++ dialog you see on the right. I've just dragged the Finder Tool over the TitleBar of the the VFP application window and then released the mouse button, so Spy++ is tracking information about _VFP.hWnd.

Figure 15 – Once you select OK in the Find Window… dialog after selecting the Messages option (see Figure 14), the Messages window quickly fills up with a listing of the message notifications for the specified window. You can see WM_ACTIVATE and WM_ACTIVATEAPP in the list. At the bottom, you can see WM_MOUSELEAVE, as I moved my mouse out of the way to make the screen shot for this illustration.
Another Spy++ feature allows you to filter the overwhelming list of messages. Select the Log Messages… option from the Spy menu.
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().
Table 2 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 2 – Here are some WM_* notifications that have an all-VFP equivalent, which is always easier to implement.
WM_NC* messages
There are many Windows message notifications that begin with WM_NC*, for which there are identical WM_* counterparts. For example, you'll see WM_NCLBUTTONDOWN and WM_NCLBUTTONUP definitions in WinUser.H, but also WM_LBUTTONDOWN and WM_LBUTTONUP.
The "NC" means "Non-Client". The Non-Client part of a Windows window includes the TitleBar, scroll bars, and borders; the part of each window that is a standard defined by the operating system itself. The client area is the area inside the Non-Client area, where you define the look-and-feel, add controls, etc.
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)
BINDEVENT(_Screen,"Resize",MyObj3,"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")
BINDEVENT(_VFP.hWnd,WM_ACTIVATE,MyObj3,"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 in the WM_* example files 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 like this: 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. Sound 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
Calling the CallWindowProc() API function
As mentioned briefly earlier in this paper, the typical action is to call the CallWindowProc() API function at the end of the delegate method. This serves a purpose somewhat analogous to a DODEFAULT() callback for VFP code, and is usually important because it signals to Windows that the current action should continue normally.
You can see how I've abstracted this functionality in WM_EventHandlers::HandleEvent():
MODIFY CLASS WM_EventHandlers OF WM_EventHandlers METHOD HandleEvent
However, in WM_PowerBroadcast::EventHandlers, you can see that I had to suppress this "default" behavior to prevent an endless cycle of message notifications. This is another example of why I recommend not assuming anything, and testing everything…
What happens in a loop?
What happens when a message notification fires while your VFP code/application is executing code in a tight loop, like a DO WHILE..ENDDO, FOR..ENDFOR, or report? My guess was that the delegate method calls would stack up in some sort of queue, and fire once the loop finished. I figured that I'd have to sprinkle DOEVENTS calls in such loops if I wanted delegates to fire during the loop.
However, as demonstrated in WM_LoopTest.PRG, my tests show that the delegate method fires immediately when the hWnd notification is received.
VFP child windows
In addition to _VFP.hWnd, _Screen.hWnd and the hWnd property of the VFP base class Form, the VFP IDE windows have their own hWnd values, although they're not directly exposed.
VFP 9 adds several SYS() functions to help access information about child windows:
- SYS(2325)
- SYS(2326)
- SYS(2327)
I can't pretend to have tried out these new functions, and I hope the help file topics in the release version of VFP 9 contains code examples. In the meantime, the code below code is a sample the Fox team made available to VFP 9 beta testers, to iterate through the child VFP IDE windows. You can use it to find the hWnd of various child windows like Document View, the Command Window, etc. Modify as you see fit.
DECLARE INTEGER FindWindow IN win32api STRING, STRING
DECLARE INTEGER FindWindowEx IN win32api;
INTEGER, INTEGER, STRING, STRING
DECLARE INTEGER GetWindowText IN WIN32API ;
INTEGER, STRING @, INTEGER
? FindWindow(0,_VFP.CAPTION+CHR(0))
? _VFP.HWND
hchild=0
showchildren(_VFP.HWND,0)
PROCEDURE showchildren(HWND AS INTEGER,nLev AS INTEGER)
LOCAL hchild,res
hchild=0
DO WHILE .T.
hchild=FindWindowEx(HWND,hchild,0,0)
IF hchild=0
EXIT
ENDIF
res=SPACE(80)
? hchild, GetWindowText(hchild,@res, ;
LEN(res)),REPLICATE(" ",nLev), ;
LEFT(res,AT(CHR(0),res)-1)
showchildren(hchild, nLev+1)
ENDDO
ENDPROC
Listing 3 – Code to iterate through the VFP child windows.
PLEASE NOTE that most, if not all VFP child IDE windows only show up in the above code if
they are NOT set to dockable (for those that support dockability).
Valid hWnd/Parameter1 values
The integer you pass as the first hWnd parameter to BINDEVENT() must be a valid VFP window handle – an _Screen.hWnd, _VFP.hWnd, or Form.hWnd. You can determine the hWnd of other Windows applications via Windows API function calls, but you cannot BINDEVENT() to messages received by those applications. …Well, you can, but VFP ignores the binding and never fires your delegate code.
The same is true when you specify an invalid/non-existent nHWnd or nMsg integer as the first or second BINDEVENT() parameter. VFP does not generate an error, and AEVENTS() indicates that the binding exists, but the binding is completely ignored.
More information
Download the code from the SDN website.
For similar-but-different information on Windows message notifications, see my article in the November 2004 issue of FoxPro Advisor Magazine.
Other code examples
I have obtained permission to include the following code examples that the Fox team made available to the VFP 9 beta testers. I've included them here as-is; no warranties are expressed or implied. Use them as you see fit.
Example 1: Detect if user tries to put machine in Stand By or Hibernation Mode
#DEFINE WM_POWERBROADCAST 0x0218
#DEFINE PBT_APMQUERYSUSPEND 0x0000
#DEFINE PBT_APMQUERYSTANDBY 0x0001
#DEFINE PBT_APMQUERYSUSPENDFAILED 0x0002
#DEFINE PBT_APMQUERYSTANDBYFAILED 0x0003
#DEFINE PBT_APMSUSPEND 0x0004
#DEFINE PBT_APMSTANDBY 0x0005
#DEFINE PBT_APMRESUMECRITICAL 0x0006
#DEFINE PBT_APMRESUMESUSPEND 0x0007
#DEFINE PBT_APMRESUMESTANDBY 0x0008
#DEFINE PBTF_APMRESUMEFROMFAILURE 0x00000001
#DEFINE PBT_APMBATTERYLOW 0x0009
#DEFINE PBT_APMPOWERSTATUSCHANGE 0x000A
#DEFINE PBT_APMOEMEVENT 0x000B
#DEFINE PBT_APMRESUMEAUTOMATIC 0x0012
*--Return BROADCAST_QUERY_DENY to deny a query ("BMQD")
#DEFINE BROADCAST_QUERY_DENY 0x424D5144
#DEFINE GWL_WNDPROC (-4)
PUBLIC goPower
goPower = NEWOBJECT('PowerClass')
BINDEVENT(_VFP.HWND, WM_POWERBROADCAST, goPower, 'HandlePowerEvent')
MESSAGEBOX('Test by trying to put machine in StandBy mode.')
*-----------------------------------------------
DEFINE CLASS PowerClass AS SESSION
nOldProc=0
PROCEDURE INIT
DECLARE INTEGER GetWindowLong IN WIN32API ;
INTEGER nHWND, ;
INTEGER nIndex
DECLARE INTEGER CallWindowProc IN WIN32API ;
INTEGER lpPrevWndFunc, ;
INTEGER nHWND, ;
INTEGER nMsg, ;
INTEGER wParam, ;
INTEGER LPARAM
THIS.nOldProc=GetWindowLong(_VFP.HWND, GWL_WNDPROC)
ENDPROC
PROCEDURE HandlePowerEvent(nHWND AS INTEGER, ;
nMsg AS INTEGER, ;
wParam AS INTEGER, ;
LPARAM AS INTEGER)
LOCAL lResult AS INTEGER
lResult=0
? nHWND, nMsg, wParam, LPARAM
DO CASE
CASE wParam = PBT_APMQUERYSUSPEND
IF MESSAGEBOX('Do you want to stand by machine?', ;
4 + 32 + 4096, 'Fox') = 6
lResult = CallWindowProc(THIS.nOldProc, nHWND, ;
nMsg, wParam, LPARAM)
ELSE
lResult = BROADCAST_QUERY_DENY && "BMQD"
ENDIF
OTHERWISE
lResult = CallWindowProc(THIS.nOldProc, nHWND, ;
nMsg, wParam, LPARAM)
ENDCASE
RETURN lResult
ENDPROC
PROCEDURE DESTROY
UNBINDEVENT(_VFP.HWND, WM_POWERBROADCAST)
ENDPROC
ENDDEFINE
Example 2: Detect Windows XP Theme Changed
#DEFINE WM_THEMECHANGED 0x031A
#DEFINE GWL_WNDPROC (-4)
PUBLIC oHandler
oHandler = NEWOBJECT('AppState')
BINDEVENT(_SCREEN.HWND, WM_THEMECHANGED, oHandler, 'HandleEvent')
MESSAGEBOX('Test by changing Themes.')
*-----------------------------------------------
DEFINE CLASS APPSTATE AS SESSION
nOldProc = 0
PROCEDURE INIT
DECLARE INTEGER GetWindowLong IN WIN32API ;
INTEGER nHWND, ;
INTEGER nIndex
DECLARE INTEGER CallWindowProc IN WIN32API ;
INTEGER lpPrevWndFunc, ;
INTEGER nHWND, ;
INTEGER nMsg, ;
INTEGER wParam, ;
INTEGER LPARAM
THIS.nOldProc = GetWindowLong(_VFP.HWND, GWL_WNDPROC)
ENDPROC
PROCEDURE HandleEvent(nHWND AS INTEGER, nMsg AS INTEGER, ;
wParam AS INTEGER, LPARAM AS INTEGER)
LOCAL lResult AS INTEGER
lResult=0
* Note: for WM_THEMECHANGED, MSDN indicates the wParam and lParam
* are reserved so can't use them.
IF nMsg = WM_THEMECHANGED
MESSAGEBOX('Theme changed...')
ENDIF
lResult = CallWindowProc(THIS.nOldProc, nHWND, nMsg, wParam, LPARAM)
RETURN lResult
ENDPROC
PROCEDURE DESTROY
UNBINDEVENT(_SCREEN.HWND, WM_THEMECHANGED)
ENDPROC
ENDDEFINE
Example 3: Detect App Switching
*-- Return BROADCAST_QUERY_DENY to deny a query ("BMQD").
#DEFINE BROADCAST_QUERY_DENY 0x424D5144
#DEFINE GWL_WNDPROC (-4)
#DEFINE WM_GETMINMAXINFO 0x0024
#DEFINE WM_WINDOWPOSCHANGING 0x0046
#DEFINE WM_ACTIVATE 0x0006
#DEFINE WA_INACTIVE 0
#DEFINE WA_ACTIVE 1
#DEFINE WA_CLICKACTIVE 2
PUBLIC oHandler, oForm, oForm2
oForm=CREATEOBJECT('f1')
oForm.SHOW()
MESSAGEBOX('Test by switching apps.')
*-----------------------------------
DEFINE CLASS f1 AS FORM
nOldProc=0
lInactive = .T.
nPosChanges = 0
ALLOWOUTPUT = .T.
SHOWWINDOW = 2
PROCEDURE INIT
_SCREEN.VISIBLE = .F.
THIS.CAPTION = 'My Custom App - ' + TRANSFORM(THIS.HWND)
DECLARE INTEGER GetWindowLong IN WIN32API ;
INTEGER nHWND, ;
INTEGER nIndex
DECLARE INTEGER CallWindowProc IN WIN32API ;
INTEGER lpPrevWndFunc, ;
INTEGER nHWND, ;
INTEGER nMsg, ;
INTEGER wParam, ;
INTEGER LPARAM
THIS.nOldProc = GetWindowLong(_VFP.HWND, GWL_WNDPROC)
BINDEVENT(THIS.HWND, WM_ACTIVATE, ;
THIS, 'HandleEvent')
BINDEVENT(THIS.HWND, WM_WINDOWPOSCHANGING, ;
THIS, 'HandleEvent')
BINDEVENT(THIS.HWND, WM_GETMINMAXINFO, ;
THIS, 'HandleEvent')
ENDPROC
PROCEDURE HandleEvent(nHWND AS INTEGER, nMsg AS INTEGER, ;
wParam AS INTEGER, ;
LPARAM AS INTEGER)
DO CASE
CASE nMsg = WM_ACTIVATE
? ICASE(wParam = WA_INACTIVE, 'InActive', ;
wParam = WA_ACTIVE, 'Active', ;
wParam = WA_CLICKACTIVE, 'ClickActive', 'Other'), ;
wParam, LPARAM
THIS.lInactive = IIF(wParam = WA_INACTIVE, .T., .F.)
THIS.nPosChanges = 0
CASE nMsg = WM_GETMINMAXINFO
? 'Max', wParam, LPARAM
CASE nMsg = WM_WINDOWPOSCHANGING
? 'PosChanging', wParam, LPARAM
IF THIS.lInactive
THIS.nPosChanges = THIS.nPosChanges + 1
* Special case for user clicking on Titlebar
* from another app which causes Max window.
IF THIS.nPosChanges = 2
THIS.lInactive = .F.
INKEY(.5, 'H')
ENDIF
ENDIF
ENDCASE
RETURN CallWindowProc(THIS.nOldProc, nHWND, nMsg, ;
wParam, LPARAM)
ENDPROC
PROCEDURE DESTROY
UNBINDEVENT(THIS.HWND, WM_ACTIVATE)
UNBINDEVENT(THIS.HWND, WM_WINDOWPOSCHANGING)
UNBINDEVENT(THIS.HWND, WM_GETMINMAXINFO)
ENDPROC
ENDDEFINE
Example 4: Detect Media Insertion (CDs, USB Harddrives), Custom Messages
This example shows how to detect for common media events such as insertion of a CD or USB Drive. In addition, there is code which shows how you can use the SHChangeNotifyRegister() call to register a window to receive notifications from the Windows shell or file system. This call allows you also to specify which types of notifications to receive (e.g., SHCNE_DISKEVENTS, SHCNE_MEDIAINSERTED, etc.). Additionally, you specify a custom Windows message event (WM_USER_SHNOTIFY) for notification. This new custom message (WM_USER_SHNOTIFY) can then be bound to using BINDEVENT().
#DEFINE WM_USER 0x0400
#DEFINE WM_USER_SHNOTIFY WM_USER+10
#DEFINE SHCNE_RENAMEITEM 0x00000001
#DEFINE SHCNE_CREATE 0x00000002
#DEFINE SHCNE_DELETE 0x00000004
#DEFINE SHCNE_MKDIR 0x00000008
#DEFINE SHCNE_RMDIR 0x00000010
#DEFINE SHCNE_MEDIAINSERTED 0x00000020
#DEFINE SHCNE_MEDIAREMOVED 0x00000040
#DEFINE SHCNE_DRIVEREMOVED 0x00000080
#DEFINE SHCNE_DRIVEADD 0x00000100
#DEFINE SHCNE_NETSHARE 0x00000200
#DEFINE SHCNE_NETUNSHARE 0x00000400
#DEFINE SHCNE_ATTRIBUTES 0x00000800
#DEFINE SHCNE_UPDATEDIR 0x00001000
#DEFINE SHCNE_UPDATEITEM 0x00002000
#DEFINE SHCNE_SERVERDISCONNECT 0x00004000
#DEFINE SHCNE_UPDATEIMAGE 0x00008000
#DEFINE SHCNE_DRIVEADDGUI 0x00010000
#DEFINE SHCNE_RENAMEFOLDER 0x00020000
#DEFINE SHCNE_FREESPACE 0x00040000
#DEFINE SHCNE_DISKEVENTS 0x0002381F
#DEFINE SHCNE_GLOBALEVENTS 0x0C0581E0
#DEFINE SHCNE_ALLEVENTS 0x7FFFFFFF
#DEFINE SHCNE_INTERRUPT 0x80000000
#DEFINE GWL_WNDPROC (-4)
#DEFINE WM_DEVICECHANGE 0x0219
#DEFINE DBT_DEVNODES_CHANGED 0x0007
#DEFINE DBT_DEVICEARRIVAL 0x8000 &&System detected a new device
#DEFINE DBT_DEVICEQUERYREMOVE 0x8001 &&wants to remove, may fail
#DEFINE DBT_DEVICEQUERYREMOVEFAILED 0x8002 &&removal aborted
#DEFINE DBT_DEVICEREMOVEPENDING 0x8003 &&about to remove, still avail.
#DEFINE DBT_DEVICEREMOVECOMPLETE 0x8004 &&device is gone
#DEFINE DBT_DEVTYP_OEM 0x00000000 &&oem-defined device type
#DEFINE DBT_DEVTYP_DEVNODE 0x00000001 &&devnode number
#DEFINE DBT_DEVTYP_VOLUME 0x00000002 &&logical volume
#DEFINE DBT_DEVTYP_PORT 0x00000003 &&serial, parallel
#DEFINE DBT_DEVTYP_NET 0x00000004 &&network resource
PUBLIC nOldProc,oMsgHandler
CLEAR
UNBINDEVENTS(0)
oMsgHandler=NEWOBJECT("MsgHandler")
BINDEVENT(_VFP.HWND,WM_DEVICECHANGE,;
osgHandler,"HandleWinMsg")
BINDEVENT(_VFP.HWND,WM_USER_SHNOTIFY, ;
oMsgHandler,"HandleWinMsg")
? "Total events:",AEVENTS(lAEvents,1)
PROCEDURE Dump(cStr AS STRING) AS NUMBER
LOCAL num,i
num=0
FOR i = 1 TO LEN(cStr)
num=num+(256^(i-1) ) * ASC(SUBSTR(cStr,i,1))
ENDFOR
RETURN INT(num)
ENDPROC
DEFINE CLASS MsgHandler AS SESSION
dwShNotify=0
nOldProc=0
PROCEDURE INIT
DECLARE INTEGER GetWindowLong IN WIN32API ;
INTEGER HWND, ;
INTEGER nIndex
DECLARE INTEGER CallWindowProc IN WIN32API ;
INTEGER lpPrevWndFunc, ;
INTEGER HWND,INTEGER Msg,;
INTEGER wParam,;
INTEGER LPARAM
DECLARE INTEGER SHGetPathFromIDList IN shell32 ;
INTEGER nItemList,;
STRING @szPath
DECLARE INTEGER SHChangeNotifyRegister IN shell32 ;
INTEGER HWND, ;
INTEGER fSources, ;
INTEGER fEvents, ;
INTEGER wMsg,;
INTEGER cEntries, ;
STRING @ SEntry
DECLARE INTEGER SHChangeNotifyDeregister IN shell32 INTEGER
cSEntry = REPLICATE(CHR(0),8)
THIS.nOldProc=GetWindowLong(_SCREEN.HWND,GWL_WNDPROC)
THIS.dwShNotify = SHChangeNotifyRegister(_VFP.HWND, ;
SHCNE_DISKEVENTS, ;
SHCNE_MEDIAINSERTED + SHCNE_MEDIAREMOVED + ;
SHCNE_DRIVEADD + SHCNE_DRIVEREMOVED, ;
WM_USER_SHNOTIFY,1,;
@cSEntry)
? "Notify", THIS.dwShNotify
ENDPROC
PROCEDURE DESTROY
IF THIS.dwShNotify != 0
IF SHChangeNotifyDeregister(THIS.dwShNotify) > 0
?"Deregister successful"
ELSE
?"Deregister ERRORd"
ENDIF
ENDIF
UNBINDEVENT(_VFP.HWND)
ENDPROC
PROCEDURE ShowBHDR(LPARAM AS INTEGER)
hdr=SYS(2600,LPARAM,12)
SIZE=Dump(LEFT(hdr,4))
num=Dump(SUBSTR(hdr,5,4))
IF num=DBT_DEVTYP_VOLUME
unitmask=Dump(SUBSTR(SYS(2600,LPARAM,SIZE),13,4))
??unitmask
FLAGS=Dump(SUBSTR(SYS(2600,LPARAM,SIZE),17,2))
??" Flags=",FLAGS
ENDIF
ENDPROC
PROCEDURE HandleWinMsg(HWND AS INTEGER, Msg AS INTEGER, ;
wParam AS INTEGER, LPARAM AS INTEGER)
lResult=0
? THIS.NAME,TRANSFORM(HWND,"@0x"), ;
TRANSFORM(Msg,"@0x"),TRANSFORM(wParam,"@0x"), ;
TRANSFORM(LPARAM,"@0x")
DO CASE
CASE Msg=WM_DEVICECHANGE
DO CASE
CASE wParam=DBT_DEVNODES_CHANGED
?"devnodes changed"
CASE wParam=DBT_DEVICEARRIVAL
?"device arrival"
THIS.ShowBHDR(LPARAM)
CASE wParam=DBT_DEVICEREMOVECOMPLETE
?"device remove complete"
THIS.ShowBHDR(LPARAM)
ENDCASE
CASE Msg=WM_USER_SHNOTIFY
DO CASE
CASE LPARAM=SHCNE_DRIVEADD
?"Drive added "
CASE LPARAM=SHCNE_DRIVEREMOVED
?"Drive removed"
CASE LPARAM=SHCNE_MEDIAINSERTED
?"Media inserted "
CASE LPARAM=SHCNE_MEDIAREMOVED
?"Media removed "
ENDCASE
num=Dump(SYS(2600,wParam,4))
szPath=SPACE(270)
SHGetPathFromIDList(num,@szPath)
szPath=LEFT(szPath,AT(CHR(0),szPath)-1)
??" path=",ALLTRIM(szPath)
lResult=CallWindowProc(THIS.nOldProc,HWND,Msg,wParam,LPARAM)
ENDCASE
RETURN lResult
ENDPROC
ENDDEFINE