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
miss.

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
focus.

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.

Mervyn.


















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

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')  
       SKIP
   next
   go top
   replace customer  with 'Abel'
   SKIP
   replace customer  with 'Baker'
   SKIP
   replace customer  with 'Charlie'
   SKIP
   replace customer  with 'Delta'
   SKIP
   replace customer  with 'Echo'
   SKIP
   use
endif

** 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
   f.readModal()
else
   f.open()
endif

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
   endwith

   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
   endwith

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

   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
   endwith


   this.rowset = this.test_grid_callback1.rowset

   function form_onClose()
      form.test_grid_callback1.active = false
      return

   function form_onOpen()
   clear
      #include winuser.h
      if type("dBGetCurrentThreadId") # "FP"
         extern CULONG dBGetCurrentThreadId() kernel32 from "GetCurrentThreadId"
      endif
      if type("dBGetWindowLong") # "FP"
         extern CLONG dBGetWindowLong(CHANDLE, CINT) user32 from "GetWindowLongW"
      endif
      if type("dBSetWindowsHookEx") # "FP"
         extern CLONG dBSetWindowsHookEx(CINT, CPTR, CHANDLE, CULONG) user32 from "SetWindowsHookExW"
      endif
      if type("dBUnhookWindowsHookEx") # "FP"
         extern CLOGICAL dBUnhookWindowsHookEx(CHANDLE) user32 from "UnhookWindowsHookEx"
      endif
      if type("dBCallNextHookEx") # "FP"
         extern CLONG dBCallNextHookEx(CHANDLE, CINT, CUINT, CUINT) user32 from "CallNextHookEx"
      endif
//      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())
      form.grid1.setFocus()
      return
        

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

   function form_canClose
      //detach the hook.
      dBUnhookWindowsHookEx(this.hook)
      return true


   function doOnEnter
      if form.test_grid_callback1.rowset.atlast()
         form.test_grid_callback1.rowset.beginAppend()
      else  
         form.test_grid_callback1.rowset.next()
      endif  
      keyboard '{home}'
      return
      

   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(form.activeControl.name) = '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
               form.test_grid_callback1.rowset.save()
             endif  
            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
         endif
      endif
      return cReturn


endclass