Handle navigation keys in TextBox inside DataGridView
TextBox works just fine if not inside DataGridView (no problem at all when using the same method adding it into TreeView for example)
Apparently the problem is in DataGridView
. It's because DataGridView
overrides the Control.ProcessKeyPreview
method:
This method is called by a child control when the child control receives a keyboard message. The child control calls this method before generating any keyboard events for the message. If this method returns true, the child control considers the message processed and does not generate any keyboard events.
The DataGridView
implementation does just that - it maintains zero or one child controls internally (EditingControl
), and when there is no such control active, it handles many keys (navigation, tab, enter, escape, etc.) by returning true
, thus preventing the child TextBox
keyboard events generation. The return value is controlled by the ProcessDataGridViewKey
method.
Since the method is virtual
, you can replace the DataGridView
with a custom derived class which overrides the aforementioned method and prevents the undesired behavior when neither the view nor the view active editor (if any) has the keyboard focus.
Something like this:
public class CustomDataGridView : DataGridView
{
bool SuppressDataGridViewKeyProcessing => ContainsFocus && !Focused &&
(EditingControl == null || !EditingControl.ContainsFocus);
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
if (SuppressDataGridViewKeyProcessing) return false;
return base.ProcessDataGridViewKey(e);
}
}
The above is just the half of the story and solves the cursor navigation and selection keys issue. However DataGridView
intercepts another key message preprocessing infrastructure method - Control.ProcessDialogKey
and handles Tab, Esc, Return, etc. keys there. So in order to prevent that, the method has to be overridden as well and redirected to the parent of the data grid view. The later needs a little reflection trickery to call a protected
method, but using one time compiled delegate at least avoids the performance hit.
With that addition, the final custom class would be like this:
public class CustomDataGridView : DataGridView
{
bool SuppressDataGridViewKeyProcessing => ContainsFocus && !Focused &&
(EditingControl == null || !EditingControl.ContainsFocus);
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
if (SuppressDataGridViewKeyProcessing) return false;
return base.ProcessDataGridViewKey(e);
}
protected override bool ProcessDialogKey(Keys keyData)
{
if (SuppressDataGridViewKeyProcessing)
{
if (Parent != null) return DefaultProcessDialogKey(Parent, keyData);
return false;
}
return base.ProcessDialogKey(keyData);
}
static readonly Func<Control, Keys, bool> DefaultProcessDialogKey =
(Func<Control, Keys, bool>)Delegate.CreateDelegate(typeof(Func<Control, Keys, bool>),
typeof(Control).GetMethod(nameof(ProcessDialogKey), BindingFlags.NonPublic | BindingFlags.Instance));
}
You can try this.
I created my own textbox and overrode method ProcessKeyMessage.
public class MyTextBox : TextBox
{
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
protected override bool ProcessKeyMessage(ref Message m)
{
if (m.Msg != WM_SYSKEYDOWN && m.Msg != WM_KEYDOWN)
{
return base.ProcessKeyMessage(ref m);
}
Keys keyData = (Keys)((int)m.WParam);
switch (keyData)
{
case Keys.Left:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.ShiftKey:
return base.ProcessKeyEventArgs(ref m);
default:
return base.ProcessKeyMessage(ref m);
}
}
}
And then you can call:
var txt = new MyTextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };