The ability for BindEvent to hook into windows message events in Visual FoxPro 9 is great. It is no doubt wrapping the SetWindowLong API function somewhere in the source and facilitates the magic by subclassing the target window. My hat goes off to the Fox Team for coming up with this. But, what if you want to modify a messagebox before it appears (such as change the button captions and centering it with the form that called it) or what if you want to create a Global Keyboard Hook? Basically, what if, as a Visual FoxPro developer, you want to take advantage of the SetWindowsHookEx API function and all the doors it opens up? Maybe I’m missing something, but BindEvent doesn’t seem to be able to do this.


I’ve got some ideas on things that I want Visual FoxPro to do long before Sedna gets here, and some of these ideas require that I am able to use the SetWindowsHookEx. So, today I decided to figure out how we (the Visual FoxPro Community) could do this.


To start this show, here’s an FLL I’ve created using Visual C++ 7.0, that gives us two more functions for Visual FoxPro. BindEventEX and UnBindEventEx. Download the vfpex.fll here. These functions will give you indirect access to SetWindowsHookEx for your Visual FoxPro applications/code. There is more to be done, but this is a good start for now.


Then, below are a couple of “cut-n-paste into a prg file and execute” examples I worked up in Visual FoxPro that use the vfpex.fll.


***************************************************
*!* MODIFY MESSAGEBOX EXAMPLE
*!*
*!* Requires: vfpex.fll
***************************************************
PUBLIC oform1


oform1=NEWOBJECT(“form1”)
oform1.Show
RETURN


DEFINE CLASS form1 AS form
 Top = 0
 Left = 0
 Height = 259
 Width = 373
 DoCreate = .T.
 Caption = “Using the New BindEventsEx – MessageBox Modification”
 WindowType = 1
 Name = “Form1”


 ADD OBJECT command1 AS commandbutton WITH ;
  Top = 216, ;
  Left = 144, ;
  Height = 27, ;
  Width = 132, ;
  Caption = “R\<un Example”, ;
  Name = “Command1”


 ADD OBJECT command2 AS commandbutton WITH ;
  Top = 216, ;
  Left = 276, ;
  Height = 27, ;
  Width = 84, ;
  Caption = “E\<xit”, ;
  Name = “Command2”


 PROCEDURE wineventhandler
  #DEFINE IDOK   1
  #DEFINE IDCANCEL  2
  #DEFINE IDABORT  3
  #DEFINE IDRETRY  4
  #DEFINE IDIGNORE  5
  #DEFINE IDYES   6
  #DEFINE IDNO   7
  #DEFINE IDCLOSE  8
  #DEFINE IDHELP   9
  #DEFINE IDTRYAGAIN 10
  #DEFINE IDCONTINUE 11
  #DEFINE IDPROMPT 65535


  IF nCode == 5
   SetWindowText(wParam, “VFP – WH_CBT HOOK”) && Title
   SetDlgItemText(wParam, IDPROMPT, “Visual FoxPro BindEventEx Example”) && Message
   SetDlgItemText(wParam, IDABORT, “VFP Rocks!”) && First Button Caption
   SetDlgItemText(wParam, IDRETRY, “Always Has”) && Second Button Caption
   SetDlgItemText(wParam, IDIGNORE, “Always Will”) && Third Button Caption


   Thisform.CenterMessageBox(wParam)


   CallNextHookEx(hHook, nCode, wParam, LPARAM) && all 4 variables exist
   UnBindEventEx()
   SET LIBRARY TO
  ELSE
   CallNextHookEx(hHook, nCode, wParam, LPARAM) && all 4 variables created by FLL
  ENDIF


  RELEASE nCode, wParam, LPARAM, hHook
 ENDPROC


 PROCEDURE buf2dword
  LPARAMETERS tcBuffer
  RETURN Asc(SUBSTR(tcBuffer, 1,1)) + ;
       Asc(SUBSTR(tcBuffer, 2,1)) * 2^8 +;
       Asc(SUBSTR(tcBuffer, 3,1)) * 2^16 +;
       Asc(SUBSTR(tcBuffer, 4,1)) * 2^24
 ENDPROC


 PROCEDURE centermessagebox
  LPARAMETERS tnwParam


  LOCAL lcBuffer, lnMsgLeft, lnMsgTop, lnMsgRight, lnMsgBottom, lnMsgWidth, lnMsgHeight


  lcBuffer = REPLI(CHR(0), 2^4)


  =GetWindowRect(tnwParam, @lcBuffer)


  lnMsgLeft = THISFORM.buf2dword(SUBSTR(lcBuffer, 1, 4))
  lnMsgTop = THISFORM.buf2dword(SUBSTR(lcBuffer, 5, 4))
  lnMsgRight = THISFORM.buf2dword(SUBSTR(lcBuffer, 9, 4))
  lnMsgBottom = THISFORM.buf2dword(SUBSTR(lcBuffer, 13, 4))
  lnMsgWidth = lnMsgRight – lnMsgLeft
  lnMsgHeight = lnMsgBottom – lnMsgTop
  lnMsgLeft = THISFORM.LEFT + (THISFORM.WIDTH – lnMsgWidth) / 2
  lnMsgTop = _screen.Top + THISFORM.TOP + (THISFORM.HEIGHT – lnMsgHeight) / 2


  MoveWindow(tnwParam, lnMsgLeft, lnMsgTop, lnMsgWidth, lnMsgHeight, .T.)
 ENDPROC


 PROCEDURE Load
  DECLARE INTEGER SetDlgItemText IN user32;
   LONG hDlg,;
   LONG nIDDlgItem,;
   STRING lpString


  DECLARE INTEGER SetWindowText IN user32;
   LONG HWND,;
   STRING  lpString


  DECLARE LONG CallNextHookEx IN user32;
   LONG, LONG, LONG, LONG


  DECLARE INTEGER MoveWindow IN user32;
   INTEGER HWND,;
   INTEGER X,;
   INTEGER Y,;
   INTEGER nWidth,;
   INTEGER nHeight,;
   INTEGER bRepaint


  DECLARE SHORT GetWindowRect IN user32 INTEGER HWND, STRING @ lpRect
 ENDPROC
 
 PROCEDURE command1.Click
  *!* Some of the various Windows Hook constants
  #define WH_MSGFILTER -1
  #define WH_JOURNALRECORD 0
  #define WH_JOURNALPLAYBACK 1
  #define WH_KEYBOARD 2
  #define WH_GETMESSAGE 3
  #define WH_CALLWNDPROC 4
  #define WH_CBT 5 && the one used here
  #define WH_SYSMSGFILTER 6
  #define WH_MOUSE 7
  #define WH_HARDWARE 8
  #define WH_DEBUG 9
  #define WH_SHELL 10
  #define WH_FOREGROUNDIDLE 11
  #define WH_CALLWNDPROCRET 12
  #define WH_KEYBOARD_LL 13
  #define WH_MOUSE_LL 14
  
  *!* Set library so BindEventEx and UnBindEventEx can be used
  *!* in VFP
  SET LIBRARY TO (LOCFILE(ADDBS(JUSTPATH(SYS(16))) + “vfpex.fll”))
  *!* You must have a Named reference to the form or object
  *!* as thisform or this cannot be used with BindEventEx
  BindEventEx(‘oform1.wineventhandler()’, WH_CBT) && SetWindowsHookEx
  *!* This messagebox will be modified before it is shown
  MESSAGEBOX(” “, 2, “”) && Just A Blank Messagebox with Abort, Retry, Ignore buttons
 ENDPROC


 PROCEDURE command2.Click
  Thisform.Release()
 ENDPROC


ENDDEFINE


***************************************************
*!* GLOBAL KEYBOARD HOOK EXAMPLE
*!*
*!* Requires: vfpex.fll
***************************************************
PUBLIC oform1


oform1=NEWOBJECT(“form1”)
oform1.Show
RETURN


DEFINE CLASS form1 AS form


 Top = 0
 Left = 1
 Height = 256
 Width = 454
 DoCreate = .T.
 Caption = “Using the New BindEventsEx – Global Keyboard Hook”
 Name = “form1”


 ADD OBJECT command1 AS commandbutton WITH ;
  Top = 216, ;
  Left = 311, ;
  Height = 27, ;
  Width = 132, ;
  Caption = “R\<un Example”, ;
  Name = “Command1”


 ADD OBJECT edit1 AS editbox WITH ;
  Height = 193, ;
  Left = 11, ;
  ReadOnly = .T., ;
  Top = 12, ;
  Width = 432, ;
  Name = “Edit1”


 PROCEDURE wineventhandler
  LOCAL HookStruct
  *!* Real basic example, just to show it’s possible
  IF wParam = 256 && keydown
   HookStruct = REPLICATE(CHR(0), 20)
   CopyMemory(@Hookstruct, lParam, 20)
   thisform.edit1.value = thisform.edit1.value + Chr(thisform.buf2dword(Hookstruct))
  ENDIF
  CallNextHookEx(hHook, nCode, wParam, lParam) && all 4 variables created by FLL
  RELEASE nCode, wParam, LPARAM, hHook
 ENDPROC


 PROCEDURE buf2dword
  LPARAMETERS tcBuffer
  RETURN Asc(SUBSTR(tcBuffer, 1,1)) + ;
       Asc(SUBSTR(tcBuffer, 2,1)) * 2^8 +;
       Asc(SUBSTR(tcBuffer, 3,1)) * 2^16 +;
       Asc(SUBSTR(tcBuffer, 4,1)) * 2^24
 ENDPROC


 PROCEDURE Load         
  DECLARE LONG CallNextHookEx IN user32;
   LONG, LONG, LONG, LONG


  DECLARE RtlMoveMemory IN kernel32 As CopyMemory;
      STRING  @ Destination,;
      INTEGER   Source,;
      INTEGER   nLength 


  SET LIBRARY TO (LOCFILE(“vfpex.fll”, “FLL”))
 ENDPROC


 PROCEDURE Destroy
  *!* Unhook when form is destroyed
  UnBindEventEx()
  SET LIBRARY TO
 ENDPROC


 PROCEDURE command1.Click
  #define WH_MSGFILTER -1
  #define WH_JOURNALRECORD 0
  #define WH_JOURNALPLAYBACK 1
  #define WH_KEYBOARD 2
  #define WH_GETMESSAGE 3
  #define WH_CALLWNDPROC 4
  #define WH_CBT 5
  #define WH_SYSMSGFILTER 6
  #define WH_MOUSE 7
  #define WH_HARDWARE 8
  #define WH_DEBUG 9
  #define WH_SHELL 10
  #define WH_FOREGROUNDIDLE 11
  #define WH_CALLWNDPROCRET 12
  #define WH_KEYBOARD_LL 13 && this is the one used
  #define WH_MOUSE_LL 14


  this.Enabled = .F.
  WAIT “Go type in another application – your keystrokes will be recorded” WINDOW TIMEOUT 2
  *!* You must have a Named reference to the form or object
  *!* as thisform or this cannot be used with BindEventEx
  BINDEVENTEX(‘oform1.wineventhandler()’, WH_KEYBOARD_LL) && SetWindowsHookEx
 ENDPROC


ENDDEFINE