Subject Re: Grid search
From Mervyn Bick <invalid@invalid.invalid>
Date Tue, 29 Sep 2020 12:29:26 +0200
Newsgroups dbase.getting-started
Attachment(s) trap_ctrlV_ctrlX.wfm

On 2020/09/28 14:35, Robbie Nott wrote:

> Hi Akshat
> Would love to see Mervyn's detailed explanation, perhaps - between the 2
> of us we could "translate" it into something understandable by us little
> people.
>
> If we could hook into other programs with this it'd be awesome !
>
> Regards
> Robbie

We're not talking about hooking into a separate program.

Installing a Windows hook allows one to intercept messages, keystrokes
or mouse actions from Windows to the specific form or control on that
form.  The type of hook determines exactly what is intercepted.

An entryfield's onKey event is not triggered for all keystrokes.  If,
for instance you want to know if Cut or Paste has altered the value in
an entryfield dBASE has no built-in way of determining this as it
happens.  Adding a keyboard hook makes this possible. An example, with
comments, is attached.

In the case of the form Akshat referred to, he wanted to use the numeric
keypad as hot keys to launch specific actions.  This was made possible
by using a keyboard hook.

Internally dBASE doesn't differentiate between the left and right shift
keys, the left and right control keys and between the numeric key pad
and the number keys in the top row of the keyboard. To dBASE shift is
shift no matter which shift key was pressed.  A keyboard hook enables on
to determine exactly which key has been pressed.


Mervyn.







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 = "<p>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.</p><p>Ctrl-C is ignored as it does not change the contents of "
      l0 += "the entryfield.  </p>"
      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