Tuesday, January 17, 2012

wxGrid editors and the arrow keys

When editing a wxGrid cell the standard behaviour is not very user-friendly: if you press one of the arrow keys you can move the caret in the editor, but you cannot select another cell. For example, a user would expect that pressing the up key would close the cell editor and move the selection to the cell above, but nothing happens.

It is possible to add this behaviour by handling the EVT_GRID_EDITOR_CREATED event for the grid. For example:

   EVT_GRID_EDITOR_CREATED( fsGridBase::OnEditorCreated )  

The OnEditorCreated() function simply sets an event handler for the EVT_KEY_DOWN event for the editor control:

 void fsGridBase::OnEditorCreated( wxGridEditorCreatedEvent& event )  
 {  
   event.GetControl()->Bind( wxEVT_KEY_DOWN, &fsGridBase::OnGridEditorKey, this );  
   event.Skip();  
 }  

The OnGridEditorKey() function can handle the EVT_KEY_DOWN event as desired by the developer. The basic rule is the following: call event.Skip() to send the event to the editor control, getting the usual behaviour.
Call GetEventHandler()->ProcessEvent(event) to send the event directly to the grid, bypassing the cell editor. This choice will let the arrow keys move to another cell.

One example could be the following:

 void fsGridBase::OnGridEditorKey( wxKeyEvent& event )  
 {  
   switch ( event.GetKeyCode() ) {  
     case WXK_DOWN:  
     case WXK_UP:  
       GetEventHandler()->ProcessEvent( event );  
       break;  
     case WXK_LEFT:  
       {  
         wxGridCellEditor* editor = GetCellEditor( GetGridCursorRow(), GetGridCursorCol() );  
         wxControl* ctrl = editor->GetControl();  
         if( ctrl->IsKindOf(CLASSINFO(wxTextCtrl)) ) {  
           wxTextCtrl* tp = dynamic_cast<wxTextCtrl*>( ctrl );  
           if( tp->GetInsertionPoint() == 0 ) {  
             // we are at the beginning of the text control so let's move to the previous column  
             GetEventHandler()->ProcessEvent( event );  
           }  
           else {  
             event.Skip();  
           }  
         }  
         editor->DecRef();  
       }  
       break;  
     case WXK_RIGHT:  
       {  
         wxGridCellEditor* editor = GetCellEditor( GetGridCursorRow(), GetGridCursorCol() );  
         wxControl* ctrl = editor->GetControl();  
         if( ctrl->IsKindOf(CLASSINFO(wxTextCtrl)) ) {  
           wxTextCtrl* tp = dynamic_cast<wxTextCtrl*>( ctrl );  
           if( tp->GetInsertionPoint() == tp->GetLastPosition() ) {  
             // we are at the end of the text control so let's move to the next column  
             GetEventHandler()->ProcessEvent( event );  
           }  
           else {  
             event.Skip();  
           }  
         }  
         editor->DecRef();  
       }  
       break;  
     default:  
       event.Skip();  
   }  
 }  

The up and down arrows will always move to another cell. The left and right arrows will move the caret in the control as usual, but if the caret is at the beginning or at the end of the control the key will move to another cell.

The example above is a good starting point, but it will cause problems if the grid uses wxGridCellChoiceEditors because that editor would not work well with keyboard keys. It will work well with the mouse, anyway.

Monday, January 9, 2012

Easily adding lines to a wxGrid

wxGrid is a good control, but users expect a behaviour more similar to a spreadsheet, so I derived my own control from wxGrid, with a number of customizations.

The first is the ability to easily add a new row at the end of the grid. With some simple code it is possible to always have an empty row at the end of the grid, so users can simply go to that line and start typing data: a new row will pop up at the end of the grid and so on...

All we need to do is handling the EVT_GRID_SELECT_CELL event:

 void fsGridBase::OnSelectCell( wxGridEvent& event )  
 {  
     int row = event.GetRow();  
     int lastRow = GetTable()->GetNumberRows() - 1;  
     if( row == lastRow ) {  
         AppendRows( 1 );  
     }  
   
   event.Skip();  
 }  
   

as soon as a cell is selected in the last row a new row is appended. This makes the grid behaviour more similar to the one of a spreadsheet.