Highlighted
Absent Member.
Absent Member.
7988 views

Traversing a datagrid using the <return> key

Jump to solution

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?

 

0 Likes
1 Solution

Accepted Solutions
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

OK, I have changed the overidden methods to search for the next non read-only cell either on the current row, the next row or the first row if wrapparound is required.

Please be aware that this only handles the case of moving forward through the grid as back-tab will not move you backwards using the same path.

Also, users can still click in a read-only cell or position to a read-only cell using the arrow keys, but they will not be able to modify the data.

Here is the new code:

      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
              set row1 to self::CurrentCell::RowIndex
             *> check current row for next no read-only cell
              perform varying col1 from col1 by 1
                 until col1 >= self::Columns::Count
                 if not self::Rows[row1]::Cells[col1]::ReadOnly
                    exit perform
                 end-if
              end-perform
             *> If following true then next column is in same row
              if col1 < self::Columns::Count
                 try
                    set self::CurrentCell to self::Rows[row1]::Cells[col1]
                 catch ex as type Exception
                 end-try  
              else
                 *> check if last row
                 if self::CurrentCell::RowIndex not = self::Rows::Count - 1
                    add 1 to row1
                 else
                    move 0 to row1
                 end-if
                 *> search next row or first row for next non read-only cell
                 perform varying col1 from 0 by 1
                    until col1 > self::Columns::Count
                    if not self::Rows[row1]::Cells[col1]::ReadOnly
                       exit perform
                    end-if
                 end-perform
                 try
                    set self::CurrentCell to self::Rows[row1]::Cells[col1]
                 catch ex as type Exception
                 end-try  
              end-if
              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
              set row1 to self::CurrentCell::RowIndex
             *> check current row for next no read-only cell
              perform varying col1 from col1 by 1
                 until col1 >= self::Columns::Count
                 if not self::Rows[row1]::Cells[col1]::ReadOnly
                    exit perform
                 end-if
              end-perform
             *> If following true then next column is in same row
              if col1 < self::Columns::Count
                 try
                    set self::CurrentCell to self::Rows[row1]::Cells[col1]
                 catch ex as type Exception
                 end-try  
              else
                 *> check if last row
                 if self::CurrentCell::RowIndex not = self::Rows::Count - 1
                    add 1 to row1
                 else
                    move 0 to row1
                 end-if
                 *> search next row or first row for next non read-only cell
                 perform varying col1 from 0 by 1
                    until col1 > self::Columns::Count
                    if not self::Rows[row1]::Cells[col1]::ReadOnly
                       exit perform
                    end-if
                 end-perform
                 try
                    set self::CurrentCell to self::Rows[row1]::Cells[col1]
                 catch ex as type Exception
                 end-try  
              end-if
              set ret-value to true
           else
              set ret-value to super::ProcessDataGridViewKey(e)
           end-if    
          goback.
      end method.      
      end class.

View solution in original post

0 Likes
18 Replies
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Absent Member.
Absent Member.

RE: Traversing a datagrid using the <return> key

Jump to solution

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?

0 Likes
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Absent Member.
Absent Member.

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Absent Member.
Absent Member.

RE: Traversing a datagrid using the <return> key

Jump to solution

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??

0 Likes
Highlighted
Absent Member.
Absent Member.

RE: Traversing a datagrid using the <return> key

Jump to solution

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.

0 Likes
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

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

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.

0 Likes
Highlighted
Absent Member.
Absent Member.

RE: Traversing a datagrid using the <return> key

Jump to solution

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!!

0 Likes
Highlighted
Micro Focus Expert
Micro Focus Expert

RE: Traversing a datagrid using the <return> key

Jump to solution

This is caused by the cell validation failing and e::Cancel being set.
It won't let you change the CurrentCell if the validation fails.

You can get around this by adding a try/catch block in the ProcessDialogKey method in CustomDataGridView around the code that tries to set the CurrentCell (two places)

      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
                 try
                    set self::CurrentCell to self::Rows[row1]::Cells[col1]
                 catch ex as type Exception
                 end-try  
              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
                       try
                          set self::CurrentCell to self::Rows[row1 + 1]::Cells[col1]
                       catch ex as type Exception
                       end-try  
                    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.

0 Likes
The opinions expressed above are the personal opinions of the authors, not of Micro Focus. By using this site, you accept the Terms of Use and Rules of Participation. Certain versions of content ("Material") accessible here may contain branding from Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017, the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE marks are the property of their respective owners.