处理DataGridView中TextBox中的导航键

问题描述 投票:7回答:5

我们有一个表格中有数据的DataGridView。为了实现快速搜索,我们将TextBox添加到DataGridView.Controls并突出显示包含TextBox文本的单元格。

但是有一个问题。 DataGridView消耗Left arrow←,Right arrow→,Home和End(带或不带Shift)键,即使光标位于TextBox中,用户也无法更改插入位置或从键盘选择文本。

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

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

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

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

c# .net winforms datagridview
5个回答
2
投票

你可以试试这个。我创建了自己的文本框并重写方法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);
        }
    }
}

然后你可以打电话:

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

2
投票

如果没有DataGridView内部的TextBox工作得很好(例如,使用相同的方法将其添加到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);
    }
}

编辑:上面只是故事的一半,解决了光标导航和选择键的问题。然而,DataGridView拦截另一个关键消息预处理基础设施方法 - Control.ProcessDialogKey并处理Tab,Esc,Return等键。因此,为了防止这种情况,必须重写该方法并将其重定向到数据网格视图的父级。后者需要一点反射技巧来调用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));
}

0
投票

您是否尝试过将TextBox添加到主窗体而不是DGV?

Controls.Add(txt);
Controls.Add(dgv);

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

txt.PreviewKeyDown += TxtOnPreviewKeyDown;
txt.KeyDown += TxtOnKeyDown;

0
投票

这是该问题的部分解决方案。 TextBox仍然没有接收本机输入的导航键,但我重现了正常的插入符号和选择行为。

PreviewKeyDownEventArgs包含有关按下的键和修饰符(Shift)的信息。对于每个组合键,我为TextBox设置了新的SelectionStartSelectionLength

private void TxtOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{       
    TextBox txt = (TextBox)sender;
    if (e.KeyCode == Keys.Home)
    {
        int idx = txt.SelectionStart;

        txt.SelectionStart = 0;
        txt.SelectionLength = e.Shift ? idx : 0;
    }
    else if (e.KeyCode == Keys.End)
    {
        int idx = txt.SelectionStart;

        if (e.Shift)
            txt.SelectionLength = txt.TextLength - idx;
        else
        {
            txt.SelectionStart = txt.TextLength;
            txt.SelectionLength = 0;
        }
    }
    else if (e.KeyCode == Keys.Left)
    {
        if (e.Shift)
        {
            if (txt.SelectionStart > 0)
            {
                txt.SelectionStart--;
                txt.SelectionLength++;
            }
        }
        else
        {
            txt.SelectionStart = Math.Max(0, txt.SelectionStart - 1);
            txt.SelectionLength = 0;
        }
    }
    else if (e.KeyCode == Keys.Right)
    {
        if (e.Shift)
            txt.SelectionLength++;
        else
        {
            txt.SelectionStart = Math.Min(txt.TextLength, txt.SelectionStart + 1);
            txt.SelectionLength = 0;
        }
    }
}

0
投票

听起来有点像徒劳无功,通过将TextBox和DataGridView控件放在UserControl中以及处理事件的小代码,可能更容易封装它们的行为。

© www.soinside.com 2019 - 2024. All rights reserved.