Subject Re: Grid Navigation
From Mervyn Bick <invalid@invalid.invalid>
Date Mon, 12 Feb 2018 11:06:27 +0200
Newsgroups dbase.getting-started
Attachment(s) test_grid_callback2.wfm

On 2018-02-10 10:28 PM, Mustansir Ghor wrote:
> Dear All
> I have grid with 5 columns.  1st column is combobox, 2nd column is entryfield, 3rd and 4th are reference display from lookuprowset. 5th is a calculated one based on item selection from 1st column and entryfiled  of 2nd column. I therefore wish to restrict navigation to only 2 columns 1st and 2nd. Can anybody advise how this can be achieved.

The fact that you have a combobox in your grid suggests that you are
using the grid for data entry and/or editing.

Over the years I've come to the conclusion that using a grid for editing
is not a good idea.  It is simply too easy to make mistakes.  I prefer
to display data in a grid and use entryfields for adding/editing fields
in conjunction with Edit, Add, Save and Abandon buttons.  Still, if you
really want to use a grid for adding/editing rows it can be done.

The only way I could restrict navigation to the first two columns of the
grid was to use the CALLBACK command.  This means this will only work
for dBASE Plus 8 and later.  (This is where someone, usually Ivar
Jesson, pops up with a Better Way than the Bick Way. :-) )

The attached example installs a keyboard "hook".  This intercepts all
keystrokes to the form by forcing Windows to execute a function in the
program in place of it's own function.  The program can then monitor all
keystrokes including those that the normal onKey and Key event handlers

Normally the keystroke is passed straight back to the Windows function
but by returning 1 instead of calling the Windows hook the keystroke is
ignored by Windows.  If the keystroke was Tab or Enter in the second
column the function doOnEnter is executed and the Tab or Enter keystroke
is not returned to Windows but is dealt with by doOnEnter..

If the user uses the mouse to click in any of the fields column 1 gets

Some points to watch

The underlying fields for columns 3 - 5 must have their readonly
property set true.

After the form has been run I've found that the sourcecode editor
crashes after changes have been made to the code.  The changes are,
however, saved first.  Close dBASE and restart.  It's a PITA but nothing
else seems to get hurt.  I'll be submitting a bug report later.


if file('test_grid_callback.dbf')
    drop table test_grid_callback

if not file('test_grid_callback.dbf')
   create table test_grid_callback (job_no character(5),customer character(15),address1 character(15),address2 character(15),address3 character(15))
   use test_grid_callback
   generate 10
   go top
   for n = 1 to 10
       replace job_no with '1'+str(N,3,0,'0')  
   go top
   replace customer  with 'Abel'
   replace customer  with 'Baker'
   replace customer  with 'Charlie'
   replace customer  with 'Delta'
   replace customer  with 'Echo'

** END HEADER -- do not remove this line
// Generated on 2018/02/12
parameter bModal
local f
f = new test_rid_callback2Form()
if (bModal)
   f.mdi = false // ensure not MDI

class test_rid_callback2Form of FORM
   with (this)
      canClose = class::FORM_CANCLOSE
      onOpen = class::FORM_ONOPEN
      onClose = class::FORM_ONCLOSE
      height = 26.7273
      left = 44.2857
      top = 3.2273
      width = 134.8571
      text = ""
      scrollBar = 2        // Auto

   this.TEST_GRID_CALLBACK1 = new QUERY(this)
   with (this.TEST_GRID_CALLBACK1)
      left = 2.0
      top = 1.0
      width = 14.0
      height = 1.0
      sql = 'select * from "test_grid_callback.DBF"'
      active = true

   with (this.TEST_GRID_CALLBACK1.rowset)
      with (fields["address1"])
         readOnly = true
      with (fields["address2"])
         readOnly = true
      with (fields["address3"])
         readOnly = true

   this.GRID1 = new GRID(this)
   with (this.GRID1)
      onLeftMouseUp = class::GRID1_ONLEFTMOUSEUP
      dataLink = form.test_grid_callback1.rowset
      height = 15.5
      left = 12.0
      top = 6.0
      width = 103.0

   this.rowset = this.test_grid_callback1.rowset

   function form_onClose() = false

   function form_onOpen()
      #include winuser.h
      if type("dBGetCurrentThreadId") # "FP"
         extern CULONG dBGetCurrentThreadId() kernel32 from "GetCurrentThreadId"
      if type("dBGetWindowLong") # "FP"
         extern CLONG dBGetWindowLong(CHANDLE, CINT) user32 from "GetWindowLongW"
      if type("dBSetWindowsHookEx") # "FP"
         extern CLONG dBSetWindowsHookEx(CINT, CPTR, CHANDLE, CULONG) user32 from "SetWindowsHookExW"
      if type("dBUnhookWindowsHookEx") # "FP"
         extern CLOGICAL dBUnhookWindowsHookEx(CHANDLE) user32 from "UnhookWindowsHookEx"
      if type("dBCallNextHookEx") # "FP"
         extern CLONG dBCallNextHookEx(CHANDLE, CINT, CUINT, CUINT) user32 from "CallNextHookEx"
//      if type("GetAsyncKeyState") # "FP"
//         extern CSHORT GetAsyncKeyState(CSHORT)  user32
//      endif
                CALLBACK CLONG hookWndProc(CINT, CUINT, CUINT) OBJECT this
      this.hookProc = GetCallAddress(class::HOOKWNDPROC)
      this.hInst = dBGetWindowLong(this.hwnd, GWL_HINSTANCE)
      this.hook = dBSetWindowsHookEx(WH_KEYBOARD, this.hookProc, this.hInst, dBGetCurrentThreadId())

   function GRID1_onLeftMouseUP(flags, col, row)
      keyboard '{tab}'
      keyboard '{home}'

   function form_canClose
      //detach the hook.
      return true

   function doOnEnter
      if form.test_grid_callback1.rowset.atlast()
      keyboard '{home}'

   function hookWndproc(hCode, wParam, lParam)
      cReturn = dBCallNextHookEx(this.hook, hCode, wParam, lParam)
      if (wParam = VK_RETURN or wParam = VK_TAB) and hcode = 0 and not bitset(lparam,31)
         if upper( = 'GRID1'  and form.grid1.currentcolumn = 2
            //tab or return in column 1 is ignored here and passed on to Windows like any other keystroke
            if form.test_grid_callback1.rowset.state = 3 and form.grid1.currentcolumn = 2
            class::doOnEnter() //Deal with tab or return in coulmn 2
            cReturn = 1 // Don't call next hook. This prevents Enter or Tab from moving cursor to next field
      return cReturn