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