How to catch the key that was pressed to end the editing of a DataGridView Cell?

I have a DataGridView and I need to customize which cell is selected after a user edits a ell. How do I know which key was pressed to end the editing of a cell ('enter', an arrow key...)? The DataGridView.KeyDown event does not seem to work.

I have tried using the EditingControlShowing event, created an event handler for the KeyDown event on the editing control. This event catches the normal keystrokes, but not the arrow keys. I'm not sure if this is missed because the editingControl is disposed before the keydown event is triggered, or becasue the KeyDown event of the editingControl simply does not capture arrow keys.

I have seen a solution that uses a customized subclass of DataGridView (How can I capture the keystroke that triggers "CellEndEdit" on a DataGridView in C#?), but I'm hoping there is a way that does not require a derived class of DataGridView. Any suggestions?


Solution 1:

This example is “specifically” in relation to getting the key pressed that triggered the grids “END” editing mode in a cell. In this example, the target keys are Enter/Return, Tab, right, left , up and down arrows. If a cell is in edit mode, then, pressing one of those keys will usually trigger the grid into ending the cells edit mode.

Unfortunately, as I am sure you are aware, It is not that easy to “capture” which key was pressed to end the edit mode… the grid sees those keys as “triggers” to end the edit mode and basically “swallows” the key and we lose it.

From a big picture, a general approach in capturing the keys pressed when a cell is in edit mode would go something like this…

  1. Wire up the grids EditingControlShowing event. This event will fire when a cell begins to enter its “edit” mode and it’s passed in… DataGridViewEditingControlShowingEventArgs object allows us to “cast” the cell to a regular TextBox and wire up its KeyPress event to capture the keys pressed while the cell is in edit mode. This is all we want to do in this event. Wire up the text boxes key press event then wait for the user to start typing.
  2. Implement the TextBoxes KeyPress event to do any special handling of the pressed keys.
  3. Unwire the TextBoxes KeyPress event when the cell ends its “edit” mode. This can be done in several events and I suggest the grid’s CellEndEdit event to turn OFF the TextBox_KeyPress event.

The above general approach should work and it is not that difficult to implement, however, the problem you describe is that even though we have cast the grid cell to a regular text box, the Key Press event will NOT fire when the user presses one of the keys previously described. And we want that key.

Therefore, given all this, I hope to show an example of how to get that key with not a lot of effort. This approach is to override the ProcessCmdKey event. This event is almost guaranteed to fire no matter what key was pressed. Also, the event will fire regardless if the cell is in edit mode or not.

Caution… you need to keep in mind that since we are overriding this processes functionality, we want to be careful and make sure that we ONLY use it when a cell is in “edit” mode. This event fires with every keystroke and we want to make damn sure that our code does not run unnecessarily.

This is not that difficult to manage. In the example below, a global variable bool CellIsBeingEdited will be used to indicate that currently a cell IS being edited. In the grids EditingControlShowing event, we will set this variable to true when a cell goes into edit mode. Then in the overridden ProcessCmdKey event, we will check to see if CellIsBeingEdited is true. If it is true then we would know that we want to “capture” that key and then simply pass it on to the base. If CellIsBeingEdited is false, then we simply pass that key data to the base ProcessCmdKey.

I would suggest that you keep the code in the ProcessCmdKey to a minimal amount. Get the key and get out. If you want to do further processing when the key pressed is the enter or tab key etc… then do that “outside” this event. Therefore, in the example below, to help, we will make a second global variable Keys LastKeyPressed;. This will keep track of the last key pressed in the edited cell… including the keys we are looking for.

This approach will actually eliminate the need to cast the cell to a text box and we do not need to wire up the cells text box key press event. All we have to do is make sure that the ProcessCmdKey event ONLY runs our code WHEN a cell is in edit mode. This should simply things a bit. This bare-bones ProcessCmdKey event may look something like…

protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
  //Debug.WriteLine("ProcessCmdKey <- Enter");
  if (CellIsBeingEdited) {
    if (keyData == Keys.Enter ||
        keyData == Keys.Right ||
        keyData == Keys.Left ||
        keyData == Keys.Up ||
        keyData == Keys.Down ||
        keyData == Keys.Tab ) {
      Debug.WriteLine("The Key pressed MAY END the cells edit mode...");
    }
    else {
      Debug.WriteLine("Cell Edit mode Keypress > " + keyData);
      // do any checking here for invalid keys pressed
      // example if you wanted to allow only numeric keys to be pressed in a column of int cells
      // simply check if the key is numeric and if not ignore it...
      //if (!char.IsDigit(((char)keyData))) {
      //  return true;
      //}
    }
    LastKeyPressed = keyData;
  }
  //Debug.WriteLine("ProcessCmdKey -> Leave");
  return base.ProcessCmdKey(ref msg, keyData);
}

We would set the global variable CellIsBeingEdited to true to start capturing the keys pressed. Therefore our simplified EditingControlShowing event may look something like…

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
  //Debug.WriteLine("DGV_EditingControlShowing <- Enter");
  CellIsBeingEdited = true;
  //Debug.WriteLine("DGV_EditingControlShowing -> Leave");
}

Now, the only thing we have to do now is wait until the user ENDs the cells edit mode by pressing one of the keys above. When the user ends the cells edit mode, then the grids CellEndEdit event will fire. In that event, we will know two things for sure… 1) the cell WAS in edit mode and 2) the edit mode ended and the key that ended the edit mode is in our global variable LastPressedKey. All we have to do is set CellIsBeingEdited to false. Therefore the grid’s CellEndEdit event may look something like…

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) {
  //Debug.WriteLine("DGV_CellEndEdit <- Enter");
  Debug.WriteLine("Pressed Key to finish editing was -> " + LastKeyPressed);
  // if you wanted to "move" the next active cell to some non-default cell
  // then in this event… you should be able to set the grids CurrentCell to whatever cell you want.
  //   Example, move the active cell to the right as opposed to down when the “Enter” key is pressed 
  CellIsBeingEdited = false;
  Debug.WriteLine("No cell is being edited! -----------");
  //Debug.WriteLine("DGV_CellEndEdit -> Leave");
}

Lastly, you may note the numerous Debug statements peppered through the code. For testing, especially grid key press events, the debug statements make it easier to trace and test your code by visually checking to make sure OUR code ONLY runs when we want it to. Example, if a cell is not in edit mode and you move around the grid with the arrow keys and you see a bunch of debug statements coming from your code… then we want to fix that. We ONLY want our code to run when a cell is in edit mode.

And Finally to complete the example with some full code to test…

DataTable GridTable;
bool CellIsBeingEdited = false;
Keys LastKeyPressed;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  GridTable = GetTable();
  FillTable(GridTable);
  dataGridView1.DataSource = GridTable;
  dataGridView1.CellEndEdit += new DataGridViewCellEventHandler(dataGridView1_CellEndEdit);
  dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
}

private DataTable GetTable() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Product_Code", typeof(Int32));
  dt.Columns.Add("Product_Name", typeof(String));
  dt.Columns.Add("Desc", typeof(String));
  dt.Columns.Add("Units", typeof(String));
  dt.Columns.Add("Quantity", typeof(Int32));
  dt.Columns.Add("Rate", typeof(Int32));
  dt.Columns.Add("Amount", typeof(Int32));
  dt.Columns.Add("Checked", typeof(Boolean));
  return dt;
}

private void FillTable(DataTable dt) {
  dt.Rows.Add(10001, "Tshirt", "Round Neck", "PCS", 100, 2500, 250000, true);
  dt.Rows.Add(10010, "Jeans", "Denim", "PCS", 100, 1100, 110000, true);
}

I Hope this makes sense and helps. If you are wanting to change the grid’s behavior by moving the next active cell to some other cell… then you should be able to do this in the grids CellEndEdit event by setting the grids CurrentCell to the cell you want. Good Luck.