Traversing a datagrid using the <return> key

In setting up a datagrid I have a grid containing 5 rows and 4 colums.

I've set my first cell to be 0,0 (no problem)

Tabbing will move in an orderly function from left to right. (no problem)

I wish the <return> key to behave like the <tab> key but the <return> key currently moves DOWN the grid rather than across.

I've added code for the 'keydown' event on the grid which states....

if NOT e::KeyCode = type "System.Windows.Forms.Keys"::Return

    exit method

end-if.

set indexOfRow to mydata::CurrentCell::RowIndex.

set indexOfColumn to mydata::CurrentCell::ColumnIndex.

set mydata::CurrentCell To mydata[indexOfColumn 1 indexOfRow]

Despite this code the cursor now moves ONE cell across and ONE LINE down.

What am I missing?

 

  • You need to set the event handled property to true.

    If you want to also go to first column of next row when in last column yoiu also need to handle that.

    Try something like the following:

    method-id dataGridView1_KeyDown final private.
    01 indexofRow binary-long.
    01 indexofColumn binary-long.
    procedure division using by value sender as object e as type System.Windows.Forms.KeyEventArgs.

              if NOT e::KeyCode = type System.Windows.Forms.Keys::Return
                exit method
              end-if.
              set indexOfRow to dataGridView1::CurrentCell::RowIndex.
              set indexOfColumn to dataGridView1::CurrentCell::ColumnIndex.

              if indexOfRow not = dataGridView1::NewRowIndex
                 if indexofColumn = dataGridView1::Columns::Count - 1
                    set indexofColumn to -1
                    add 1 to indexofRow          
                 end-if
                 set dataGridView1::CurrentCell To dataGridView1[indexOfColumn 1, indexOfRow]
              end-if
              set e::Handled to true
          end method.

  • Hi Chris.

    I've now got the code to do as I want as regards traversing the grid BUT...

    Currently I have an event for 'cellValidating'.

    On data entry into a cell and pressing the <RET> the code inside the 'keyDown' event DOES NOT GET processed. Only the code in the 'cellValidating' method is done.

    The upshot of this is that the cursor moves one row down and does not traverse across.

    Pressing the <RET> key on any cell WITHOUT entering data DOES fire up the keyDown method and the cursor behaves as expected.

    How do I fix this?

  • Seems like you have to also trap the CellEndEdit event when you are also doing validation.

    Add the following event handler:

    method-id dataGridView1_CellEndEdit final private.
    procedure division using by value sender as object e as type  System.Windows.Forms.DataGridViewCellEventArgs.

          invoke type SendKeys::Send("{TAB}")

    end method.

  • Actually the only way that I have been able to get this to work successfully when editing and validating using the Enter Key as a tab is to subclass the dataGridView class and override the methods ProcessDialogKey and ProcessDataGridViewKey so that I can capture the enter and process it as a tab.

    In the following example there is a second class near the bottom called CustomDataGridView in which I inherit from dataGridView and then override the two methods.

    When this is compiled a new control called CustomDataGridView will appear in the Windows Forms Toolbox and then it can be selected and pasted to the form just like the standard dataGridView only the enter key will behave like a tab key.

    I added the class and rebuilt a project that already used a dataGridView and I was able to modify the Designer.cbl file for the form to get it to reference the CustomDataGridView class instead of the dataGridView class in the two places where the class is used and after rebuilding it worked OK.

    I definately don't recommend that you modify the designer.cbl file directly as it can cause problems but I was able to get it to work without having to delete the old dataGridView and replace with the new one.

          class-id testdatagrid.Form1 is partial
                    inherits type System.Windows.Forms.Form.
          working-storage section.
          01 my-Table.
             03 my-Items         occurs 10.
                05 Table-Code    pic x(3).
                05 Table-Desc    pic x(20).
                05 Table-Value   pic S9(7)V99.
          01 num-items           binary-long.
          01 sub-1               binary-long.
          01 table-value-decimal decimal.

          method-id NEW.
          procedure division.
              invoke self::InitializeComponent
              invoke self::loadTable    
              goback.
          end method.

          method-id dataGridView1_CellValidating final private.
          01 rownum   binary-long.
          01 tempstring string.
          procedure division using by value sender as object e as type System.Windows.Forms.DataGridViewCellValidatingEventArgs.
              if e::ColumnIndex = 0
                 set rownum to e::RowIndex
                 set tempstring to e::FormattedValue
                 if tempstring::CompareTo("444") > 0
                    set e::Cancel to true
                    set datagridview1::Rows[rownum]::ErrorText to "The value must be <= 444"
                 else
                    invoke dataGridView1::CommitEdit(type DataGridViewDataErrorContexts::CurrentCellChange)
                    set datagridview1::Rows[rownum]::ErrorText to ""
                 end-if
              end-if
          end method.

          method-id loadTable public.
          procedure division.
              move "111" to table-code(1)
              move "Table Item 1" to table-desc(1)
              move 1234.50 to table-value(1)
              move "222" to table-code(2)
              move "Table Item 2" to table-desc(2)
              move -9876.99 to table-value(2)
              move "333" to table-code(3)
              move "Table Item 3" to table-desc(3)
              move 1324.00 to table-value(3)
              move "444" to table-code(4)
              move "Table Item 4" to table-desc(4)
              move 1234.50 to table-value(4)
              move "555" to table-code(5)
              move "Table Item 5" to table-desc(5)
              move -123.75 to table-value(5)
              move 5 to num-items
              goback.
          end method.

          method-id Form1_Shown final private.
          procedure division using by value sender as object e as type System.EventArgs.
              perform varying sub-1 from 1 by 1
                 until sub-1 > num-items
                 invoke dataGridView1::Rows::Add
                 set dataGridView1::Rows[sub-1 - 1]::Cells["CodeNum"]::Value to table-code(sub-1)
                 set dataGridView1::Rows[sub-1 - 1]::Cells["Description"]::Value to table-desc(sub-1)
                 move table-value(sub-1) to table-value-decimal
                 set dataGridView1::Rows[sub-1 - 1]::Cells["Amount"]::Value to table-value-decimal
              end-perform  
              set dataGridView1::CurrentCell To dataGridView1[0, 0]
              invoke dataGridView1::Focus
          end method.

          method-id btnExit_Click final private.
          procedure division using by value sender as object e as type System.EventArgs.
              invoke self::Close
          end method.

          method-id btnSave_Click final private.
          procedure division using by value sender as object e as type System.EventArgs.
              move 1 to sub-1
              perform varying myRow as type DataGridViewRow thru dataGridView1::Rows
                 if myRow::Cells["CodeNum"]::Value = null
                    exit perform
                 end-if
                 move myRow::Cells["CodeNum"]::Value to table-code(sub-1)
                 move myRow::Cells["Description"]::Value to table-desc(sub-1)
                 set table-value(sub-1) to type System.Convert::ToDecimal(myRow::Cells["Amount"]::Value)
                 add 1 to sub-1
              end-perform
          end method.
          end class.

          class-id CustomDataGridView inherits type DataGridView.
          working-storage section.

          method-id ProcessDialogKey override.
          local-storage section.
          01 keycode type Keys.
          procedure division using by value keyData as type Keys
                             returning ret-value as condition-value.
           *> Extract the key code from the key value.
               set keycode to keyData b-and type Keys::KeyCode
           *> Handle the ENTER key as if it were a tab key.  
               if keycode = type Keys::Enter
                  set ret-value to self::ProcessTabKey(keyData)
               else
                  set ret-value to super::ProcessDialogKey(keyData)
               end-if
               goback.          
          end method.

          method-id ProcessDataGridViewKey override.
          procedure division using by value e as type KeyEventArgs
                             returning ret-value as condition-value.
           *> Handle the ENTER key as if it were a tab key.  
              if e::KeyCode = type Keys::Enter
                 set ret-value to self::ProcessTabKey(e::KeyData)
              else
                 set ret-value to super::ProcessDataGridViewKey(e)
              end-if  
              goback.
          end method.      
          end class.

  • Hi Chris,

    Copied your code into a test form, replaced the datagrid with the 'Custom' one.

    Wouldn't compile!

    Have initiated a incident (2611308). Have attached the solution to the incident.

    Could you check it out please to see whats wrong?

    Thanks.

  • Hi Mark,

    The problem is that you changed the name of the control in the .designer.cbl file to CustomDataGridView1 but you are refererencing it in the Form program as dataGridView1.

    These must have the same name as it is the same control that you are referencing in both.

    It is actually not necessary to change the name of the control at all.
    You simply have to change the type of the control in two places in the designer.cbl file:

    The line where the object reference is defined:

    01 dataGridView1 type CustomDataGridView.

    and the line where it is instantiated:

    set dataGridView1 to new CustomDataGridView

    I gave you these instructions just to convert any of your existing forms that already have the dataGridView defined.

    If you are creating a new dataGridView then just drag the new CustomDataGridView control from the toolbox instead of the original dataGridView and then you do not have to make any modifications at all.

    Thanks.

  • Ok...we're getting there!!!

    One final piece of the puzzle....

    My table is 4 cols wide (0...3).

    Cols 0 & 1 are information columns (Description & Default Value). Cols 2 & 3 are where the user will 'edit' the values.

    I have code which tracks the <tab> and <enter> keys so that:

    1) On form entry the cursor positions on Col 2 Row 0 by way of...

          method-id Form1_Shown final private.

          procedure division using by value sender as object e as type System.EventArgs.

                  invoke mydata::Focus()

                  set noOfDataRows to mydata::RowCount()

                  subtract 1 from noOfDataRows              

                  set mydata::CurrentCell To mydata[2 0]

          end method.

    2) As the user clicks on the <tab> or entry key WITHOUT changing the data already inthe cell I have code which positions the cursor on the next grid position like so...

          method-id mydata_CellEndEdit final private.

          procedure division using by value sender as object e as type System.Windows.Forms.DataGridViewCellEventArgs.

              invoke type SendKeys::Send("{TAB}")

          end method.

    &&

          method-id mydata_KeyDown final private.

          procedure division using by value sender as object e as type System.Windows.Forms.KeyEventArgs.

              if NOT e::KeyCode = type "System.Windows.Forms.Keys"::Return

                 exit method

              end-if.

              set indexOfRow to mydata::CurrentCell::RowIndex.

              set indexOfColumn to mydata::CurrentCell::ColumnIndex  

              if indexofColumn = 3                                                  *>> Last column of data

                 if indexOfRow = noOfDataRows                                       *>> Last row of data

                    set mydata::CurrentCell To mydata[2 0]                          *>> Go back round

                 else

                    add 1 to indexofRow

                    set mydata::CurrentCell To mydata[2, indexOfRow]

                 end-if  

              else

                 if indexOfRow not = mydata::NewRowIndex

                    if indexofColumn = mydata::Columns::Count - 1

                       set indexofColumn to -1

                       add 1 to indexofRow          

                    end-if

                    set mydata::CurrentCell To mydata[indexOfColumn 1, indexOfRow]

                  end-if

               end-if.  

              set e::Handled to true.

          end method.

    This stuff all now works great EXCEPT....

    On pressing the <RET> key in Col 3 (last column) AFTER editing data IN COL 4 the cursor shoots back to COL 0 of the next row and NOT COL 2.

    How can I force it to COL 2 if I've edited data in COL3 in the previous row??

  • Apologies...

    The text...

    On pressing the <RET> key in Col 3 (last column) AFTER editing data IN COL 4 the cursor shoots back to COL 0 of the next row and NOT COL 2.

    should read as...

    On pressing the <RET> key in Col 3 (last column) AFTER editing data IN COL 3 the cursor shoots back to COL 0 of the next row and NOT COL 2.

  • You don't like to make things easy, do you Mark?

    You can make the enter key skip over columns that are defined with the ReadOnly property by modifying the CustomDataGrid class as follows:

          class-id CustomDataGridView inherits type DataGridView.
          working-storage section.

          method-id ProcessDialogKey override.
          local-storage section.
          01 keycode type Keys.
          01 col1 binary-long.
          01 row1 binary-long.
          procedure division using by value keyData as type Keys
                             returning ret-value as condition-value.
           *> Extract the key code from the key value.
               set keycode to keyData b-and type Keys::KeyCode
           *> Handle the ENTER key as if it were a Tab key.  
              if keycode = type Keys::Tab or keycode = type Keys::Enter
                  set col1 to self::CurrentCell::ColumnIndex 1
                  perform varying col1 from col1 by 1
                     until col1 >= self::Columns::Count
                     if not self::Columns[col1]::ReadOnly
                        exit perform
                     end-if
                  end-perform
                  if col1 < self::Columns::Count
                     set row1 to self::CurrentCell::RowIndex
                     set self::CurrentCell to self::Rows[row1]::Cells[col1]
                  else
                     if self::CurrentCell::RowIndex not = self::Rows::Count - 1
                        perform varying col1 from 0 by 1
                           until col1 > self::CurrentCell::ColumnIndex
                           if not self::Columns[col1]::ReadOnly
                              exit perform
                           end-if
                        end-perform
                        if col1 <= self::CurrentCell::ColumnIndex
                           set row1 to self::CurrentCell::RowIndex
                           set self::CurrentCell to self::Rows[row1 1]::Cells[col1]
                        end-if
                     end-if
                  end-if
                 *> set ret-value to self::ProcessTabKey(keyData)
                   set ret-value to true
               else
                  set ret-value to super::ProcessDialogKey(keyData)
               end-if
               goback.          
          end method.

          method-id ProcessDataGridViewKey override.
          01 col1 binary-long.
          01 row1 binary-long.
          procedure division using by value e as type KeyEventArgs
                             returning ret-value as condition-value.
           *> Handle the ENTER key as if it were a Tab key.  
              if e::KeyCode = type Keys::Tab or e::KeyCode = type Keys::Enter
                  set col1 to self::CurrentCell::ColumnIndex 1
                  perform varying col1 from col1 by 1
                     until col1 >= self::Columns::Count
                     if not self::Columns[col1]::ReadOnly
                        exit perform
                     end-if
                  end-perform
                  if col1 < self::Columns::Count
                     set row1 to self::CurrentCell::RowIndex
                     set self::CurrentCell to self::Rows[row1]::Cells[col1]
                  else
                     if self::CurrentCell::RowIndex not = self::Rows::Count - 1
                        perform varying col1 from 0 by 1
                           until col1 > self::CurrentCell::ColumnIndex
                           if not self::Columns[col1]::ReadOnly
                              exit perform
                           end-if
                        end-perform
                        if col1 <= self::CurrentCell::ColumnIndex
                           set row1 to self::CurrentCell::RowIndex
                           set self::CurrentCell to self::Rows[row1 1]::Cells[col1]
                        end-if
                     end-if
                  end-if
                  set ret-value to true
               else
                  set ret-value to super::ProcessDataGridViewKey(e)
               end-if    
              goback.
          end method.      
          end class.

  • Looking good.....however :(

    On entering a NON NUMERIC value into any of the cells in cols 2 & 3 and pressing the <Tab> or <Enter> key I get a box error stating...

    'Operation did not succeed because the program cannot commit or quit a cell value change'

    I'm sure we're getting there!!