Manejar las teclas de navegación en TextBox dentro de DataGridView

Manejar las teclas de navegación en TextBox dentro de DataGridView

Aparentemente el problema está en DataGridView . Es porque DataGridView anula el Control.ProcessKeyPreview método:

El DataGridView La implementación hace exactamente eso:mantiene cero o un control secundario internamente (EditingControl ), y cuando no hay dicho control activo, maneja muchas teclas (navegación, tabulador, entrar, escapar, etc.) devolviendo true , evitando así que el niño TextBox Generación de eventos de teclado. El valor devuelto está controlado por el ProcessDataGridViewKey método.

Dado que el método es virtual , puede reemplazar el DataGridView con una clase derivada personalizada que anula el método mencionado anteriormente y evita el comportamiento no deseado cuando ni la vista ni el editor activo de la vista (si lo hay) tienen el foco del teclado.

Algo como esto:

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);
    }
}

Lo anterior es solo la mitad de la historia y resuelve el problema de las teclas de selección y navegación del cursor. Sin embargo DataGridView intercepta otro método de infraestructura de preprocesamiento de mensajes clave - Control.ProcessDialogKey y maneja Tab , Esc , Volver , etc. claves allí. Entonces, para evitar eso, el método también debe anularse y redirigirse al padre de la vista de cuadrícula de datos. El último necesita un pequeño truco de reflexión para llamar a un protected método, pero usar un delegado compilado una vez al menos evita el impacto en el rendimiento.

Con esa adición, la clase personalizada final sería así:

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));
}

Puedes probar esto.

Creé mi propio cuadro de texto y anulé el método 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);
        }
    }
}

Y luego puedes llamar:

var txt = new MyTextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };