clear if __version__ < 8 msgbox("This form will only run in dBase Pluse 8 or higher.","Error") return endif /* An entryfield's onKey event is not triggered by all keystokes. Using a keyboard hook can address this as ALL keystokes can be examined. If, for instance, a programmer requires an action to take place if the value in an entryfield is changed the normal course would be to use the entryfield's onKey event handler. This works when data in typed in but, unfortunately, an entryfield's onKey event doesn't fire for ALL keystrokes. The event doesn't fire, for instance, if characters are cut from the field using ctrl-X or are pasted in using ctrl-V. The onChange event can't be used here as it only fires when the entryfield loses focus. Adding a keyboard hook allows the programmer to specifically test for ctrl-X and ctrl-V and then take the necessaryaction. In this example a keyboard hook is installed for the form but monitoring for ctrl-X and ctrl-V only ocurrs when an entryfield has focus. An option is, however, to create a custom entryfield which incorporates an individual keyboard hook. */ #include winuser.h #define VK_V 0x56 //not defined in winuser.h #define VK_X 0x58 //not defined in winuser.h ** END HEADER -- do not remove this line // // Generated on 2020-09-27 // parameter bModal local f f = new trap_ctrlV_ctrlXForm() if (bModal) f.mdi = false // ensure not MDI f.readModal() else f.open() endif class trap_ctrlV_ctrlXForm of FORM with (this) onOpen = class::FORM_ONOPEN onClose = class::FORM_ONCLOSE height = 17.0 left = 20.1429 top = 0.6364 width = 42.1429 text = "" endwith this.ENTRYFIELD1 = new ENTRYFIELD(this) with (this.ENTRYFIELD1) onChange = class::ENTRYFIELD1_ONCHANGE onKey = class::ENTRYFIELD1_ONKEY height = 1.0 left = 6.7143 top = 2.4545 width = 20.2857 value = "Entryfield1" endwith this.ENTRYFIELD2 = new ENTRYFIELD(this) with (this.ENTRYFIELD2) onChange = class::ENTRYFIELD2_ONCHANGE onKey = class::ENTRYFIELD2_ONKEY height = 1.0 left = 6.7143 top = 5.2273 width = 20.2857 value = "Entryfield2" endwith this.ENTRYFIELD3 = new ENTRYFIELD(this) with (this.ENTRYFIELD3) onChange = class::ENTRYFIELD3_ONCHANGE onKey = class::ENTRYFIELD3_ONKEY height = 1.0 left = 6.7143 top = 8.4091 width = 20.2857 value = "Entryfield3" endwith this.TEXT1 = new TEXT(this) with (this.TEXT1) height = 5.2273 left = 4.0 top = 10.5 width = 33.0 l0 = "
An entryfield's onkey and key events are not triggered by ctrl-X and ctrl-V. This program uses ctrl-X and ctrl-V, which do change the contents of the entryfield, to trigger the onKey event.
Ctrl-C is ignored as it does not change the contents of " l0 += "the entryfield.
" text = l0 endwith function form_onOpen //The following create prototypes for various functions in WIndows. Once //a function has been EXTERN'd dBASE can use the function as if it is actually //part of dBASE. To create the prototype the function name is case sensitive. //Once dBASE has accepted the function it is no longer case sensitive. if type("GetCurrentThreadId") # "FP" extern CULONG GetCurrentThreadId() kernel32 from "GetCurrentThreadId" endif if type("GetAsyncKeyState") # "FP" extern CSHORT GetAsyncKeyState(CINT) user32 endif if type("GetWindowLong") # "FP" extern CLONG GetWindowLong(CHANDLE, CINT) user32 from "GetWindowLongA" endif if type("SetWindowsHookEx") # "FP" extern CLONG SetWindowsHookEx(CINT, CPTR, CHANDLE, CULONG) user32 from "SetWindowsHookExA" endif if type("UnhookWindowsHookEx") # "FP" extern CLOGICAL UnhookWindowsHookEx(CHANDLE) user32 endif if type("CallNextHookEx") # "FP" extern CLONG CallNextHookEx(CHANDLE, CINT, CUINT, CUINT) user32 endif CALLBACK CLONG hookWndProc(CINT, CUINT, CUINT) OBJECT this //CALLBACK is a dBASE command telling dBASE we are about to change //a pointer in in Windows to execute our procedure (hookWndProc) rather //than the native Windows procedure. It sets up the prototype for the //function in dBASE much as FUNCTION does for user-defined functions //or EXTERN for external functions. this.hookProc = GetCallAddress(class::HOOKWNDPROC) //GetCallAddress is a dBASE function to get the address of our replacement //function in memory. hInst = GetWindowLong(this.hwnd, GWL_HINSTANCE) //GWL_HINSTANCE is a Windows constant containing a LONG value (64-bits) // which points it to the memory location where the handle assigned to the form // is kept. GetWIndowsLong() is a Windows function which retrieves LONG values. this.hook = SetWindowsHookEx(WH_KEYBOARD, this.hookProc, hInst, GetCurrentThreadId()) //A hook allows a programmer to divert messages from Windows to a user program. //SetWindowsHook is a Windows function which set a hook, in this case a keyboard hook and //this.hookProc is the address of our funtion. hInst is the pointer to this form and //GetCurrentThreadId is information that Windows needs. form.entryfield1.setFocus() return function hookWndproc(hCode, wParam, lParam) //All keyboard messages to the form are redirected here. t1 = wParam // virtual key code. t2 = lParam // bit 31 = 0 keydown, bit 31 = 1 keyup if hCode = 0 and bitset(t2,31) = true //keyup //Each keystoke is passed twice. Once on key down and again on key up //Each keystroke is, in turn, passed twice. Once with hCode = 0 and then with hCode = 3 //The test above makes sure we only react once instead of four times. if "ENTRYFIELD"$form.activecontrol.name and not (t1=VK_TAB or t1=VK_RETURN or (t1>=VK_PRIOR and t1<=VK_DOWN)) //For this form we only want to trap keystokes to entryfields so we check for "ENTRYFIELD" in the //active control's name. //Exclude tab, enter, arrow keys, pgup, pgdwn, home and end keys as these do trigger the onKey event. //Deal with these keys in the relevant onKey event handler if GetAsyncKeyState(VK_CONTROL) <> 0 and (t1 = VK_V or t1 = VK_X) //v or x ASCII values. Defined in form's header //ctrl-c excluded so as not to trigger onKey event //GetAsyncKeyState is a Windows function which reurns a value if a key's virtual // value is passed to it. If the value returned is not 0 it means the key is down. form.activecontrol.onKey(t1,0,false,true) //if ctrl-V or ctrl-X was pressed pass the value for the key and the control key state //to the control's onkey event handler and execute it. The program now knows that //the contents of the control has changed and can deal with it. endif endif endif return CallNextHookEx(this.hook, hCode, wParam, lParam) //CallNextHookEx is a Windows function which passes the parameter //on to the next process in Windows. If we return 1 instead of //executing CallNextHook() the parameters don't get passed on. //This effectively "swallows" the keystroke. To "swallow" a keystroke // 1 must be returned for all 4 messages generated by a single keystroke. function ENTRYFIELD1_onChange() ?'ef1 onChange' return function ENTRYFIELD1_onKey(nChar, nPosition,bShift,bControl) ?'ef1 onKey '+iif(bControl = true,'Ctrl+','')+chr(nChar) return function ENTRYFIELD2_onChange() ?'ef2 onChange' return function ENTRYFIELD2_onKey(nChar, nPosition,bShift,bControl) ?'ef2 onKey '+iif(bControl = true,'Ctrl+','')+chr(nChar) return function ENTRYFIELD3_onChange() ?'ef3 onChange' return function ENTRYFIELD3_onKey(nChar, nPosition,bShift,bControl) ?'ef3 onKey '+iif(bControl = true,'Ctrl+','')+chr(nChar) return function form_onClose UnhookWindowsHookEx(this.hook) release callback hookWndProc object this //House keeping. Put things back the way you found them. return endclass