C# 为什么我的自定义DataGridViewEditingControl从未收到Enter键的按键事件?

C# 为什么我的自定义DataGridViewEditingControl从未收到Enter键的按键事件?,c#,winforms,datagridview,C#,Winforms,Datagridview,我有一个自定义的DataGridView编辑控件,它使用Enter键实现一些功能。它使用以下代码实现IDataGridViewEditingControl接口方法“`EditingControlWantInputKey”: public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey) { switch (keyData & Keys.KeyCode) {

我有一个自定义的DataGridView编辑控件,它使用Enter键实现一些功能。它使用以下代码实现
IDataGridViewEditingControl
接口方法“`EditingControlWantInputKey”:

public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
{
    switch (keyData & Keys.KeyCode)
    {
        case Keys.Left:
        case Keys.Right:
        case Keys.Up:
        case Keys.Down:
        case Keys.Home:
        case Keys.End:
        case Keys.Enter:
        case Keys.Delete:
            return true;

        default:
            return !dataGridViewWantsInputKey;
    }
}
但是,它从未收到Enter键的KeyDown事件。我在
editingControlWantInputKey
方法中放置了一个条件断点,以查看数据网格视图是否调用过它,以确定我是否只想响应Enter键,以确定它从未被调用

在我的编辑控件中,我重写了
ProcessCmdKey
方法,以查看底层
消息是否正在使用以下代码发送到控件

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if ((msg.Msg == User32.WM_KEYDOWN) &&
        ((Keys)msg.WParam == Keys.Enter))
    {
        Console.WriteLine("GOT HERE");
    }

    return base.ProcessCmdKey(ref msg, keyData);
}
它实际上是在获取消息,但它从未发送到
OnKeyDown
方法

有一次,我认为
DataGridView
可能注册了
IMessageFilter
来处理密钥本身,但如果是这样,控件将获得对
ProcessCmdKey
的调用(我通过添加自己的
IMessageFilter
来检查这一点)

是否有人知道DataGridView正在做什么来阻止我的自定义编辑控件获得对
OnKeyDown
的调用,以及是否有方法更改该行为

我能想到的唯一一件事就是自己从
ProcessCmdKey
方法处理消息的路由,但这让我感觉很不舒服

[编辑]

要回答金的评论:

编辑控件是
文本框
的自定义子类。自定义子类只需添加更高级的自动完成功能即可工作(比内置的
文本框支持更好)。自定义文本框使用KeyDown事件来了解用户何时想要选择建议的自动完成项目。它在应用程序中的其他几个地方也被使用,并且已经在生产代码中使用了几个月(因此我很有信心它不是罪魁祸首)

[编辑-带示例代码]

我构造了一个最小的程序,它似乎显示了这一点。创建一个新的WinForms项目并在表单中放置DataGridView。很抱歉代码墙,但这是看到效果所需的最小值

如果运行,您会注意到,
EditingControlWantsInputKey
将永远不会为
CustomEditingControl
调用。当输入键代码时,不会调用
OnKeyDown
,但会调用
ProcessCmdKey

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        dataGridView.EditingControlShowing += this.DataGridView_EditingControlShowing;
    }

    private void DataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
    {
        // Must remove first to avoid adding the same event handler twice.
        e.Control.KeyDown -= this.EditingControl_KeyDown;
        e.Control.KeyDown += this.EditingControl_KeyDown;
    }

    private void EditingControl_KeyDown(object sender, KeyEventArgs e)
    {
        Console.WriteLine(e.KeyData);
    }
}

public class CustomEditingControl : DataGridViewTextBoxEditingControl
{
    public override bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
    {
        if ((Keys.KeyCode & keyData) == Keys.Enter)
        {
            Console.WriteLine("EditingControlWantsInputKey: Enter");
        }
        else
        {
            Console.WriteLine("EditingControlWantsInputKey: Other");
        }

        return base.EditingControlWantsInputKey(keyData, dataGridViewWantsInputKey);
    }

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        const int WM_KEYDOWN = 0x0100;

        if ((msg.Msg == WM_KEYDOWN) &&
            ((Keys)msg.WParam == Keys.Enter))
        {
            Console.WriteLine("ProcessCmdKey: Enter");
        }

        return base.ProcessCmdKey(ref msg, keyData);
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter)
        {
            Console.WriteLine("OnKeyDown: Enter");
        }
        else
        {
            Console.WriteLine("OnKeyDown: Other");
        }

        base.OnKeyDown(e);
    }
}

public class CustomDataGridViewTextBoxCell : DataGridViewTextBoxCell
{
    public override Type EditType
    {
        get
        {
            return typeof(CustomEditingControl);
        }
    }
}

public class CustomDataGridViewColumn : DataGridViewTextBoxColumn
{
    public CustomDataGridViewColumn()
    {
        this.CellTemplate = new CustomDataGridViewTextBoxCell();
    }
}
[编辑-包含更多发现]

我向消息链添加了尽可能多的日志记录。对于普通文本框,按Enter键会产生如下消息:

PreFilterMessage: Return
PreProcessMessage: Return
ProcessCmdKey: Return
WndProc: Return
ProcessKeyPreview: Return
OnKeyDown: Return
private sealed class RouteKeyDownMessageFilter : IMessageFilter
{
    private readonly Control mControl;
    private readonly Keys mKey;

    public RouteKeyDownMessageFilter(Control control, Keys key)
    {
        this.mControl = control;
        this.mKey = (Keys.KeyCode & key);
    }

    public bool PreFilterMessage(ref Message m)
    {
        if ((m.Msg == WM_KEYDOWN) &&
            (m.HWnd == this.mControl.Handle) &&
            (((Keys)m.WParam & Keys.KeyCode) == this.mKey))
        {
            SendMessage(m.HWnd, m.Msg, m.WParam, m.LParam);
        }

        return false;
    }

    public const int WM_KEYDOWN = 0x0100;

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
对于编辑控件,按“1”键(例如)可生成以下轨迹:

PreFilterMessage: D1
PreProcessMessage: D1
ProcessCmdKey: D1
WndProc: D1
EditingControlWantsInputKey: D1
ProcessKeyPreview: D1
OnKeyDown: D1
EditingControl_KeyDown: D1 (This is from the hooked up event handler)
对于编辑控件,按“回车”键可生成以下轨迹

PreFilterMessage: Return
PreProcessMessage: Return
ProcessCmdKey: Return

因此,有东西(可能是DataGridView)正在捕获
ProcessCmdKey
WndProc
方法之间的消息。

尝试覆盖函数ProcessDialogKey


见参考资料

经过大量的挖掘,我找到了一个似乎有点不合时宜的解决方案

创建一个新的
IMessageFilter
,我得到了以下结果:

PreFilterMessage: Return
PreProcessMessage: Return
ProcessCmdKey: Return
WndProc: Return
ProcessKeyPreview: Return
OnKeyDown: Return
private sealed class RouteKeyDownMessageFilter : IMessageFilter
{
    private readonly Control mControl;
    private readonly Keys mKey;

    public RouteKeyDownMessageFilter(Control control, Keys key)
    {
        this.mControl = control;
        this.mKey = (Keys.KeyCode & key);
    }

    public bool PreFilterMessage(ref Message m)
    {
        if ((m.Msg == WM_KEYDOWN) &&
            (m.HWnd == this.mControl.Handle) &&
            (((Keys)m.WParam & Keys.KeyCode) == this.mKey))
        {
            SendMessage(m.HWnd, m.Msg, m.WParam, m.LParam);
        }

        return false;
    }

    public const int WM_KEYDOWN = 0x0100;

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
然后,在实现
IDataGridViewEditingControl
接口的类中,我将此附加逻辑添加到
EditingControlDataGridView
属性中,以注册/取消注册消息过滤器

public DataGridView EditingControlDataGridView
{
    get { return this.mEditingControlDataGridView; }
    set
    {
        if (this.mEditingControlDataGridView != null)
        {
            this.mEditingControlDataGridView.Disposed -= this.EditingControlDataGridView_Disposed;

            Application.RemoveMessageFilter(this.mCurrentMessageFilter);
            this.mCurrentMessageFilter = null;
        }

        this.mEditingControlDataGridView = value;

        if (this.mEditingControlDataGridView != null)
        {
            this.mCurrentMessageFilter = new RouteKeyDownMessageFilter(this, Keys.Return);
            Application.AddMessageFilter(this.mCurrentMessageFilter);

            this.mEditingControlDataGridView.Disposed += this.EditingControlDataGridView_Disposed;
        }
    }
}

private void EditingControlDataGridView_Disposed(object sender, EventArgs e)
{
    if (this.mCurrentMessageFilter != null)
    {
        Application.RemoveMessageFilter(this.mCurrentMessageFilter);
    }
}
因此,所有这些都可以确保消息过滤器在编辑控件分配数据网格视图时处于适当位置。它还为数据网格视图的
Dispose
事件注册一个事件处理程序,以确保在清理数据网格视图时取消注册消息过滤器

消息筛选器本身只接受一个控件和一个触发器键,并在获取给定控件的消息时重新路由消息。参考
控件的
而不是
句柄
,这一点很重要,因为
句柄
可能在
控件
的生命周期内发生变化

我有点困惑,重新发送消息会起到作用,但当这一切就绪(以及适当的日志记录)时,我会得到以下消息链接:

RoutingMessageFilter.PreFilterMessage: Return
RoutingMessageFilter, Routing Message.
WndProc: Return
EditingControlWantsInputKey: Return
PreProcessMessage: Return
ProcessCmdKey: Return
WndProc: Return
OnKeyDown: Return
EditingControl_KeyDown: Return

这个消息流唯一奇怪的地方是
WndProc
被调用了两次。我不知道为什么会发生这种情况,但它似乎没有任何不良副作用。更重要的是,
editingcontrolwantInputKey
现在可以与编辑控件中的
OnKeyDown
方法一起正确调用。

覆盖OnPreviewKeyDown为我实现了这一点

PreviewKeyDown上受保护的覆盖子项(如PreviewKeyDownEventArgs) 'MyBase.OnPreviewKeyDown(e) e、 IsInputKey=True
End Sub

要实现用户控件(例如从面板派生的控件)接收按键消息,它必须首先具有键盘焦点

这可以通过几行来完成。下面的代码通过使用Tab键或鼠标来聚焦控件

public Constructor() 
{
    SetStyle(ControlStyles.Selectable, true);
    TabStop = true;
}
protected override void OnMouseDown(MouseEventArgs e) 
{
    Focus();
    base.OnMouseDown(e);
}

您的意思是说只需输入
键,其他键就可以接收了吗?您的
DataGridViewEditingControl
继承的基类是什么?可能基类重写并过滤掉了
Enter
键关闭事件。@KingKing所有其他键都可以正常工作。@KingKing在问题中添加了一个编辑来回答您的第二条评论。我发现默认编辑控件也会发生这种情况,
Enter
键似乎被DataGridView吞没。我认为您可能必须在尝试时在
ProcessCmdKey
中捕捉回车键。我认为这没有帮助,您应该自己尝试,并确保在回答之前它能工作。OP希望EditingControl能够接收
Enter
按键事件并处理我所做的一切