为什么我的自定义DataGridViewEditingControl永远不会收到Enter键的KeyDown事件?

时间:2013-06-26 16:35:02

标签: c# winforms datagridview

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

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事件。我在EditingControlWantsInputKey方法中放置了一个条件断点,以查看数据网格视图是否曾调用它来查明我是否只想响应Enter键而发现它永远不会被调用。

在我的编辑控件中,我覆盖了ProcessCmdKey方法,以查看是否使用以下代码将基础Message发送到控件。

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方法处理消息的路由,但这只是感觉到了hackish。

[编辑]

回答King King的评论:

编辑控件是TextBox的自定义子类。自定义子类只需添加更高级的自动完成功能(优于内置支持TextBox)。自定义文本框使用KeyDown事件来了解用户何时想要选择建议的自动完成项。它被用于应用程序中的其他几个地方已经在生产代码中使用了几个月(所以我非常有信心它不是罪魁祸首)。

[编辑 - 带示例代码]

我构建了一个最小程序,似乎表明这种情况正在发生。创建一个新的WinForms项目并在表单中放置一个DataGridView。对于代码墙感到抱歉,但这只是看到效果所需的极小。

如果运行,您会注意到,当密钥代码为Enter时,EditingControlWantsInputKey将永远不会调用CustomEditingControlOnKeyDown将不会被调用,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

对于编辑控件,按“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)

对于编辑控件,按“Enter”键会产生以下路径

PreFilterMessage: Return
PreProcessMessage: Return
ProcessCmdKey: Return

所以某些事情(可能是DataGridView)正在捕获ProcessCmdKeyWndProc方法之间的消息。

4 个答案:

答案 0 :(得分:1)

经过多次挖掘,我找到了一个似乎没有什么黑客的解决方案。

创建一个新的IMessageFilter,我最终得到了以下内容:

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事件注册事件处理程序,以确保在清理数据网格视图时取消注册消息过滤器。

消息过滤器本身只接受控制和触发键,并在获取给定控件的消息时重新路由消息。引用Control而不是Handle的{​​{1}}非常重要,因为Control的生命周期中Handle可能会发生变化。

我有点困惑的是,重新发送消息可以解决问题,但是当这个到位时(以及相应的日志记录),我得到以下消息链接:

Control

关于此消息流的唯一奇怪之处是RoutingMessageFilter.PreFilterMessage: Return RoutingMessageFilter, Routing Message. WndProc: Return EditingControlWantsInputKey: Return PreProcessMessage: Return ProcessCmdKey: Return WndProc: Return OnKeyDown: Return EditingControl_KeyDown: Return 被调用两次。我不确定为什么会这样,但它似乎没有任何不良副作用。更重要的是,WndProc现在可以在编辑控件中正确调用EditingControlWantsInputKey方法。

答案 1 :(得分:0)

答案 2 :(得分:0)

覆盖OnPreviewKeyDown为我做了诀窍......

受保护的覆盖Sub OnPreviewKeyDown(e作为PreviewKeyDownEventArgs)         “MyBase.OnPreviewKeyDown(e)中         e.IsInputKey = True 结束子

答案 3 :(得分:-1)

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

这可以用几行完成。以下代码使用Tab键或鼠标来控制控件。

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