2

我有一个自定义 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可能已经注册 aIMessageFilter来处理密钥本身,但如果是这种情况,控件会调用到ProcessCmdKey(我自己通过添加自己的 来检查这个IMessageFilter)。

有谁知道 DataGridView 正在做什么来防止我的自定义编辑控件被调用,OnKeyDown以及是否有办法改变这种行为?

我唯一能想到的就是自己从ProcessCmdKey方法中处理消息的路由,但这只是感觉很糟糕。

[编辑]

回答 King King 的评论:

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

[编辑 - 带有示例代码]

我已经构建了一个最小的程序,它似乎显示了这种情况。创建一个新的 WinForms 项目并在 Form 中放置一个 DataGridView。对不起,代码墙,但这只是看到效果所需的最低限度。

如果运行,您会注意到当键码为 Enter 时将EditingControlWantsInputKey永远不会被调用,不会被调用,但会被调用。CustomEditingControlOnKeyDownProcessCmdKey

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

4 回答 4

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引用 the而不是 the很重要,Handle因为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的方法一起调用。OnKeyDown

于 2013-06-27T12:12:47.167 回答
0

尝试覆盖函数 ProcessDialogKey

请参阅参考资料.. http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.processdialogkey(v=vs.85).aspx

于 2013-06-27T01:50:52.027 回答
0

覆盖 OnPreviewKeyDown 对我有用...

Protected Overrides Sub OnPreviewKeyDown(e As PreviewKeyDownEventArgs) 'MyBase.OnPreviewKeyDown(e) e.IsInputKey = True End Sub

于 2015-12-30T19:14:35.333 回答
-1

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

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

public Constructor() 
{
    SetStyle(ControlStyles.Selectable, true);
    TabStop = true;
}
protected override void OnMouseDown(MouseEventArgs e) 
{
    Focus();
    base.OnMouseDown(e);
}
于 2016-12-13T03:52:58.890 回答