处理DataGridView中TextBox中的导航键

ASh*_*ASh 8 .net c# datagridview winforms

我们DataGridView在表单中有数据.为了使我们增加了快速搜索TextBoxDataGridView.Controls并突出其包含从文本框文本的单元格.

但是有一个问题.DataGridView的消耗Left箭头,Right箭头,HomeEnd(有或无Shift)键,即使光标在文本框,并且用户不能更改光标处或选择从键盘文本.

TextBox生成PreviewKeyDown事件,没有更多的事情发生

简化代码:

public partial class TestForm : Form
{
    public TestForm()
    {
        InitializeComponent();
        Width = 400;
        Height = 400;

        var txt = new TextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };
        var dgv = new DataGridView
        {
            Dock = DockStyle.Fill,
            ColumnCount = 3,
            RowCount = 5
        };
        dgv.Controls.Add(txt);            
        Controls.Add(dgv);

        dgv.PreviewKeyDown += DgvOnPreviewKeyDown;
        dgv.KeyDown += DgvOnKeyDown;       

        txt.PreviewKeyDown += TxtOnPreviewKeyDown;
        txt.KeyDown += TxtOnKeyDown;
    }
    private void DgvOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
        Debug.WriteLine(String.Format("Dgv Key Preview {0}", e.KeyCode));
        e.IsInputKey = true;
    }

    private void DgvOnKeyDown(object sender, KeyEventArgs e)
    {
        Debug.WriteLine(String.Format("Dgv Key {0}", e.KeyCode));
    }

    private void TxtOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
        Debug.WriteLine(String.Format("Txt Key Preview {0}", e.KeyCode));            
    }

    private void TxtOnKeyDown(object sender, KeyEventArgs e)
    {
        Debug.WriteLine(String.Format("Txt Key {0}", e.KeyCode));
    }
}
Run Code Online (Sandbox Code Playgroud)

键入123TextBox,然后尝试Right箭头,Left箭头EndHome.DataGridView更改选定的单元格但TextBox插入符号不移动.

如果没有DataGridView内部的TextBox工作得很好(例如,使用相同的方法将其添加到TreeView中时完全没问题).TextBox与浏览器中的快速搜索面板类似,必须位于DGV之上.将TextBox添加到表单(或者更具体地说,添加到DGV父级)会创建自己的一组问题(跟踪位置,大小,可见性......),这是不可接受的.

可以做些什么来确保TextBox接收这些键并更改插入位置或选择文本?

Iva*_*oev 5

如果 TextBox 不在 DataGridView 内部,则可以正常工作(例如,使用相同方法将其添加到 TreeView 中时完全没有问题)

显然问题出在DataGridView. 这是因为DataGridView重写了该Control.ProcessKeyPreview方法:

当子控件收到键盘消息时,子控件将调用此方法。子控件在为消息生成任何键盘事件之前调用此方法。如果此方法返回 true,则子控件认为消息已处理并且不会生成任何键盘事件。

DataGridView实现就是这样做的 - 它在内部维护零个或一个子控件 ( EditingControl),并且当没有此类控件处于活动状态时,它通过返回 来处理许多键(导航、制表符、输入、转义等)true,从而防止子TextBox键盘事件生成。返回值由方法控制ProcessDataGridViewKey

由于该方法是virtual,因此您可以将 替换DataGridView为自定义派生类,该派生类将重写上述方法,并在视图和视图活动编辑器(如果有)都没有键盘焦点时防止出现不良行为。

像这样的东西:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

以上只是故事的一半,解决了光标导航和选择键的问题。然而DataGridView,拦截另一个关键消息预处理基础设施方法 -Control.ProcessDialogKey并在那里处理TabEscReturn等键。因此,为了防止这种情况,还必须重写该方法并将其重定向到数据网格视图的父级。后者需要一点反射技巧来调用protected方法,但使用一次编译的委托至少可以避免性能损失。

添加后,最终的自定义类将如下所示:

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));
}
Run Code Online (Sandbox Code Playgroud)


小智 3

你可以试试这个。

我创建了自己的文本框并覆盖方法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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以调用:

var txt = new MyTextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };
Run Code Online (Sandbox Code Playgroud)