BindEvent on Steroids 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 ENDDEFINEThat is CRAIG MVP (Again congratulations Craig) post!