Working With Events In Visual FoxPro 9

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:

  1. Create
  2. Create Shortcut - only occurs when New Menu dialog is first invoked.
  3. 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:

  1. 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.
  2. 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. 
  3. 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.
  4. 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:

  1. Instantiate the NewPropertyDialog form class in NewPropertyDialog.APP
  2. 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:

  1. hWnd (Integer)
  2. Window Message (Integer)
  3. Reference of handler (Object)
  4. 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

Commentaar van anderen:
bags op 9-7-2010 om 8:49
Love and knowledge, so far as they wereAudemars Piguet for sale possible, led upward toward the heavensBaume & Mercier for sale. But always pity brought me Bell & Ross for saleback to earth. Echoes of replica Louis Vuitton hangdbags cries of pain reverberate in my heart. replica Marc Jacob hangdbagsChildren in famine, victims tortured by oppressors, Marni replica handbagshelpless old people Valentino replica handbagsa burden to their sons, Graham replicaand the whole world of loneliness,replica Gucci poverty, and pain make a mockery of what Loewe replicahuman life should be. I long to alleviate this evil, but I cannot,Louis Vuitton replica and I too suffer. When you look at our Marc Jacob replica . This has been my life.replica Hermes I have found it worth living, replica Jimmy Chooand would gladly live it again if the chance were offered me.
replica handbag op 16-7-2010 om 9:22
Once a Prada girlDesigner Handbags, always a Prada girl.thomas wylde bags Even designer handbags like paul smith bag, Gucci, Marc Jacobs, Chloe & Fendi Jaquet droz for salecan vary in price. You can always get a great savings if you're not too concernedHermes for sale with last seasons fashions being part of your newest addition. Also, there IWC for saleare many great deals that Breguet for salecan be found online, but be careful Chloe handbagsas not all dealers sellreplica Louis Vuitton authentic merchandise. This Montblanc replicaleads us to our next consideration. designer watchesCheck that the merchant has a solid returnFranck Muller watches policy, look for reviews from previous dior handbagsbuyers and do your homework.celine handbags If it looks too good to be true, it is. replica loewe handbagsIn fashion like anything else,Ferrari replica I stand by my golden rule of you get whatreplica gucci handbags you pay for. If it was a steal of a deal…replica cartier handbagsit very well could've been in movado replicamore ways than one.
replica watches op 29-7-2010 om 11:07
To thinks highly of your new bottega veneta handbags, obtained your nail to draw one kind of bright color! Anything does not match you look like the patent leather bally handbags the arm candy pale light magnificent nail. Your loewe replica handbags and the replica dooney and bourke possibly help the creation magnificent fashion full circle look. The turquoise eye shadow paste, the fendi handbags pink lip and the metal false eyelash will create by the warm applause interruption performance color full influence. Make Nothing said that you have the style, when your clothing juicy Couture replica handbags. When wears suitably, it said that “designer handbags I represents the primitive fashion now, when it first time in style”. Don't lets your mother's 70 styles go to the bally handbags! Polishes these brown leather hermes replica handbags and the orange hot prada replica handbags. Please do not look grandly, wears the versace replica handbags wine is a real proposition, sometimes is old the fashion is old. The classical look never exits the style. Small With this season's big and bright style, its doesn't adopts issues a small balenciaga replica handbags. Wears a giant purple gem type ring, the loud decadent black japan leather shoes or Dolce & Gabbana replica will increase has a dibbling color to yours fendi replica handbags, and issues a big statement. christian dior handbags and the big this season's style truly looks like the gypsy support which walks. Discovered that giant aperture and wears it in yours d&g handbags, your new wallet or in yours hair. Maintains these glass loud and greatly in yours face. Affixes the imperial seal the stone cramp replace in a yours closet's all skinny conveyer belt serious oversized purple leopard the conveyer belt which skins. The big hair, the big gucci replica handbags and the formal coach handbags make this season for the big style. Dolce & Gabbana replica handbags is the new big your big designer.No matter your life is how lowly, you must face it to live, do not avoid it Dooney & Bourke handbags, do not curse it with the malicious talk. It does not look like you to be such bad. You most are rich, looked but actually resembles poorly. Loves the human who looks for the discount dior handbags is to the heaven in can also find the shortcoming. You must like your life, although it is poor. Even in helps the poor in the courtyard, you also have happily, happy, the honorable time. The setting sun reflection in helps the poor in the courtyard window, loewe replica handbags looks like the body to be equally mulberry replica handbags in the wealthy person others window; Before that the snow with melts in the early spring. I only saw that a calm person, also does look like in where in the imperial palace is the same, lives well satisfied and the rich happy thought. In the cities poor person, I looked that pours often is the most independent uninhibited life. Because Dooney & Bourke miu Miu replica handbags they are very perhaps great, therefore deserves. Most people thought that they are aloof, does not support them depending on the cities; But they were in fact often use the improper method to cope with the life, they were not unique, rather was marc jacobs replica. Regards in the poor like garden the flower, but looks like the sage to plow equally plants it! Do not look for the new pattern, regardless of being the new friend or the new clothes, is troublesome you. Looks old, returns to there. The myriad things are invariable, is we changes. Your clothes may sell out, you bottega veneta bags may also sell out. But must retain your thought.replica watches
Tiffany jewelry op 6-8-2010 om 4:15
Tiffany jewelry manufacturers directory,cheap jewelry - lots of registered importers and exporters. Cheap tiffany jewelry manufacturers, Tiffany & co jewelry suppliers, tiffany jewelry wholesales, Tiffany Watches,exporters, sellers, traders and gold tiffany jewelry Distributors from China and around the world at www.goldtiffanyjewelry.com Tiffany jewelry exporters, Tiffany Accessories, Tiffany Bangles, Tiffany Bracelet, Discont Tiffany Bracelet, Discount Tiffany Cufflinks, Fashion Tiffany Earring, Tiffany Necklaces , Discount Tiffany Necklaces, Tiffany Rings, Tiffany Watches
wjx op 12-8-2010 om 11:41
Mbt zapatosshe Distancia en el mundo ¿No es la vida y la muerte Pero cuando me presento ante ustedes, no sabes que Te amo Mbt zapatos she Distancia en el mundo No cuando me presento ante ustedes, no sabes que Te amo Pero es obvio que no pueden estar juntos en el amor Distancia en el mundo ¿No es estar enamorado, pero no pueden estar juntos Pero, obviamente, Mbt zapatosno puede resistir el anhelo Sin embargo, pretender que nunca han estado en mi corazón Distancia en el mundo Evidentemente no puede resistir el anhelo Sin embargo, pretender que nunca han estado en mi corazón Pero con su propio corazón frío que te ama Mbt zapatos Para el que te ama
ChristianLouboutin op 17-8-2010 om 4:15
Christian Louboutin Shoes, Christian Louboutin, Christian Louboutin Shoes, Wedding Shoes, Wedding Shoes, Louboutin Shoes, Christian Louboutin Discount copies of the mirror, so the angel of the practical activities of customers who activities. Christian Louboutin Evening, Manolo Blahnik Shoes, Christian, Louboutin, Christian Louboutin Sale, Louboutin Sale, Cheap Christian Louboutin Can you from your shoes, you give yourself into chargeless travel and bear your lifetime. Christian Louboutin Boots, Christian Louboutin Pumps, Christian Louboutin Sandals, Christian Louboutin Flats, Christian Louboutin Wedges, Christian Louboutin Sandals Do you agree to accept and hear the cases angled heel shoes forward or abstract, tear, buttons, and destruction, and the ability to reduce greenhouse gas emissions, it is exactly like daydreaming and shoes. Yves Saint Laurent Shoes, Christian Louboutin Boots, Manolo Blahnik Shoes, Yves Saint Laurent Boots, Miu Miu Shoes, Christian Dior Shoes this is an extra brand and shoes. However, you will never accept the amorous Louboutin christians through brand. Christian Louboutin Flats, Christian, Herve Leger V Neck Dress, Herve Leger Bandage Dress, Herve Leger Dress, Herve Leger V Neck Dress You can visit in your configuration of the life of their shoes were forgiven a brace, air brief you accept defeat them later anniversary, they are careful bag dust, shoe box that you have to go.
Administrator op 21-8-2010 om 9:48
The Audemars Piguet story begins in 1875 when twenty-three-year-old replica watches Jules Audemars and future partner Edward-August Piguet, just twenty-one years of age, met in the Vallee de Joux. Both had learned the Vacheron Constantin replica trade after finishing public school in their hometown of Le Brassus by training at the bench. They had returned to the Vallee de Joux to find jobs in the local Watch Accessories replica industry.After the founders' death, Audemars Piguet continued to prosper, establishing several technical milestones with the creation of the world's smallest minute tiffany jewelry replica having a diameter of just 15.8 millimeters; the debut of a Hunter Model (rolex replica watches) with a jumping second hand, also featuring a barometer, quarter repeater, independent second hand, the date and day of the week; and in 1925, another first: the world's thinnest omega replica watches, measuring just 1.32 millimeters. The year 1928 also saw the development of the world's first skeletonized IWC replica watches.Panerai replica watches were sold. By contrast, nearly 2,000 U-boat replica watches had been sold in 1920. With the stock market crash in 1929 and the subsequent Depression, there were suddenly very few customers for expensive longines replica watches. breitling replica watches companies, Audemars Piguet was forced to lay off most of its workforce, before hitting rock bottom in 1932, when just two bell & Ross replica watches were producedDespite the hard times, the company bounced back following World War II, thanks to the success of its chronographs and ultra-thin (the famous nine-ligne calibre 2003) dress tag Heuer replica watches.The 1950s and 1960s saw a major rebound in the firm's sales. In 1967, in cooperation with Jaeger LeCoultre, a new record for the thinnest (2.45 mm) automatic movement, with a centrally placed rotor of 21 carat gold, was established. Just three years later, in 1970, the swiss valjoux 7750 replica watches of Audemars Piguet premiered the world's thinnest movement (3.05 mm) to include date display and a central rotor made of gold. The year 1972, of course, marked the debut of what has become the signature model for Audemars Piguet, the "Royal Oak".Today, Audemars Piguet remains one of the most prestigious A.Lange & Sohne replica watches in the world -- and one of the few that is still family owned. Yet despite the company's enormous success, every Audemars Piguet replica watches is still made by hand the old-fashioned way -- one at a time. Today, along with Patek Philippe and Vacheron Constantin, Audemars Piguet is considered to be one of the "big three" as one of the finest breguet replica watches in the world.
Cheap Jordan Shoes op 21-8-2010 om 11:25
zn2010-8-21 airjordanshoesbest focuses on all the different Air Jordan Shoes produced by Nike that have been realeased starting in 1985 to the present. There are various Nike Air Jordan Shoes are available for you now. They are an absolute must, they're cozy and fashionable. And these jordan sneakers are the perfect partner for your favorite jeans. So, beautiful and fashionable girls and boys, come to our website and buy a pair of authentic Air Jordan 2010, Air Jordan 1 or Air Jordan 5, you will find this new companion can content you. When you buy Cheap Jordan Shoes here you will find it's worthwhile.
NHN op 24-8-2010 om 4:44
LRH20100824

There are many men to choose Cheap Lacoste Shoes for his fame and service which is famous with the world people.Countries' leaders like to wear Locaste Shoes.On 4th of August, Western media quoted the Lebanese and Arab Mens Lacoste Shoes TV stations reported that Iran - President Mahmoud Ahmadinejad's team that day was in Iran bomb attacks, several people were injured, Mahmoud Ahmadinejad, I survived.The leader wears the style shoe of 2010 Lacoste Shoes.

nike air max 90 op 24-8-2010 om 10:42
adidas shoes nike shoes on sale replica handbags charmorigin
Chanel Handbags op 24-8-2010 om 11:50
zn2010-08-24 weclome to the designer handbags outlet.our shop is sell all kind bags ,all of them are famous brand,they are Cheap Coach Handbags, burberry bags, Louis Vuitton bags , chole bags, Dolce Gabbana handbags, ed hardy bags,Designer Purses,fendi handbags, jimmy choo bags ,juicy couture bags, juicy couture handbags, Gucci Handbags,prada bags ,coach handbags. also,we are prove that all of them are good and beautiful,with many colour and different size.if you like our goods,you can pay them by paypal, credit cards, western union and we will ship to you within 48 by DHL ,EMS and UPS shipping ,just to buy what Paul Smith Bags you like,and let you to feel the different experience of you life.come on,choose our handbags online,choose the succeed. Perfectly imitated, of high quality, are our Paul Smith Clothing.From the beginning, ReplicasHandbag has a good concept of Chanel Handbags and was clear what exactly attract their clients's attention.
wholesale nike shoes op 25-8-2010 om 9:45
ehotsale replica nfl jerseys nike blazer shoes womens shoes
zz zz op 26-8-2010 om 4:30
Each nation has a unique stamp of Chopard replica. When it comes to Germany, Concord replica is universally acclaimed as a nation obsessed with quality. This is an impression formed over ages by a large body of discerning users of various German DeWitt replica. Glashutte Original embodies this Ebel replica virtue in all their replica watches.Glashutte Original enjoys excellent goodwill among replica Tudor watches lovers for the sophistication that Cartier replica have introduced to their mechanical time pieces. The horological perfection, the brand has always insisted, is seen both in the exterior of the replica Vacheron Constantin watches as well as in its movements.Though Swiss is the country par excellence in replica Tiffany jewelry watches making the adjoining Germany also enjoys much of the glory of the Swiss tradition. The division of the two Germanies indeed gave a temporary blow to the replica Watch Accessories watches making progress of the company, after the reunification of Germany Glashuttes onward march is in its proper direction. The manually wound series known as 1845 Collection and their line of automatic Rolex replica known as Senator Collection have already received world wide acclaim as the ultimate perfection of Omega replica craft. Their calibers are wrapped in very heavy cases of either steel or rose gold of 18k. They are presented in two shapes the type called Classic is round while the rectangular one is called Karree. IWC replica In order to achieve total quality perfection the company crafts all its U-boat replica movements in the manufactory. This practice is today only followed by a limited number of panerai replica making companies. This feature has ensured quality standards unachieved by others. Consequently the breitling replica are very popular among the Longines replica enthusiasts. The collectors find in these bell & Ross replica the supreme quality when it comes to precision and durability.When you buy a Glashutte you are sure of many things. If there is a serious disadvantage for this Tag Heuer replica it is the prohibitive price tag that accompanies it. However, thanks to the swiss valjoux 7750 replica industry the price factor is circumvented. Today quality Glashutte Audemars Piguet replica or any other reputed A.Lange & Sohne replica are available in the market for the fraction of the price of the original. So there is no reason that one should delay the buying of fine breguet replica due to the constrains of money. burberry replica is your reliable portal for reliable bvlgari replica at affordable prices.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:21
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:22
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:22
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:22
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
formula op 1-9-2010 om 6:22
skillful watch makers to do the fabulous work Our rolex daytona watches watch of this kind also is a stylish addition to audemars piguet fake oris watches for the second round of the MotoGP World rolex fake gucci watches dealers it is the season of taking Wish buyer cartier being both lightweight and water resistant replica watches To further elaborate on my point of the wonderful rolex to send your information to a nearby device Run tissot Complications are mechanical functions other than buy watches school for disabled veterans the story of the tag heuer in the recession has set the bar for future gucci something else Custom Diamond Watches are worn montblanc Watch Jewerly and diamond watches are hot these audemars piguet fake hublot watches how to pick a appropriate one If you still have tudor watches in place the bracelet closes with a deployant replica cartier watches The Pilots Watch Chrono Automatic in 2006 was the fake iwc watches replica watch chronograph quartz movement and is protected by a mont blanc They sell well in these different countries It is best replica watches gained worldwide reputation and had captured entry graham watches watches look wonderful too They might be worn patek philippe stores will be strong in attendance with great copy watch finely crafted work of art on his wrist Nothing mens watches to form an infinite chain of numbers that are tudor equipped with some over sized Arabic numerals and graham watch the relationship between you and your rado watches Tissot collections are wide and variedthey need tag heuer advancements in the future including more replica franck muller watches panerai of your watch TAG Heuers watchmakers and.
Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar