65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ProcessKeyPreview 在用户控件中重写 Keydown

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (13投票s)

2007 年 12 月 31 日

CPOL

2分钟阅读

viewsIcon

106861

如何在窗体或用户控件中轻松重写按键处理

引言

最近我在处理一个非常复杂的用户控件,它上面有很多子控件,我想能够在一个地方(主用户控件)重写 `keydown` 事件的处理。

我将 keydown 事件添加到我的用户控件,但注意到它从未被触发。这似乎是因为子控件在处理它们,并且没有将它们传递给主控件。

在窗体上,您可以将 `Form.KeyPreview` 设置为 `True`,这将允许窗体在将密钥事件传递给具有焦点的控件之前接收它们。不幸的是,这在用户控件上不可用。

在互联网上搜索发现,当在用户控件中重写 `ProcessKeyPreview` 事件时,它允许你在子控件获取键盘消息之前捕获它们。

不幸的是,`ProcessKeyPreview` 不是很友好,它传递给你 Windows 消息。这意味着你需要知道消息编号,处理重复键,处理控制键等等。

我对框架进行了一些反思,发现实际上很容易将消息转换为标准的 `keydown` 和 `keyup` 事件,这使得编码更加容易。

我认为有些人可能会觉得它有用。

Using the Code

要使用该代码,只需将其粘贴到您的控件中。然后你只需要决定你是否需要 `Keydown` 和/或 `keyup` 事件,并根据你的需要来实现它们。

//----------------------------------------------
// Define the PeekMessage API call
//----------------------------------------------

private struct MSG
{
    public IntPtr hwnd;
    public int message;
    public IntPtr wParam;
    public IntPtr lParam;
    public int time;
    public int pt_x;
    public int pt_y;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool PeekMessage([In, Out] ref MSG msg, 
    HandleRef hwnd, int msgMin, int msgMax, int remove);

//----------------------------------------------
 
/// <summary> 
/// Trap any keypress before child controls get hold of them
/// </summary>
/// <param name="m">Windows message</param>
/// <returns>True if the keypress is handled</returns>
protected override bool ProcessKeyPreview(ref Message m)
{
    const int WM_KEYDOWN = 0x100;
    const int WM_KEYUP = 0x101;
    const int WM_CHAR = 0x102;
    const int WM_SYSCHAR = 0x106;
    const int WM_SYSKEYDOWN = 0x104;
    const int WM_SYSKEYUP = 0x105;
    const int WM_IME_CHAR = 0x286;

    KeyEventArgs e = null;

    if ((m.Msg != WM_CHAR) && (m.Msg != WM_SYSCHAR) && (m.Msg != WM_IME_CHAR))
    {
        e = new KeyEventArgs(((Keys)((int)((long)m.WParam))) | ModifierKeys);
        if ((m.Msg == WM_KEYDOWN) || (m.Msg == WM_SYSKEYDOWN))
        {
            TrappedKeyDown(e);
        }
        //else
        //{
        //    TrappedKeyUp(e);
        //}
            
        // Remove any WM_CHAR type messages if supresskeypress is true.
        if (e.SuppressKeyPress)
        {
            this.RemovePendingMessages(WM_CHAR, WM_CHAR);
            this.RemovePendingMessages(WM_SYSCHAR, WM_SYSCHAR);
            this.RemovePendingMessages(WM_IME_CHAR, WM_IME_CHAR);
        }

        if (e.Handled)
        {
            return e.Handled;
        }
    }
    return base.ProcessKeyPreview(ref m);
}

private void RemovePendingMessages(int msgMin, int msgMax)
{
    if (!this.IsDisposed)
    {
        MSG msg = new MSG();
        IntPtr handle = this.Handle;
        while (PeekMessage(ref msg, 
        new HandleRef(this, handle), msgMin, msgMax, 1))
        {
        }
    }
}

/// <summary>
/// This routine gets called if a keydown has been trapped 
/// before a child control can get it.
/// </summary>
/// <param name="e"></param>
private void TrappedKeyDown(KeyEventArgs e)
{
    if (e.KeyCode == Keys.A)
    {
        e.Handled = true;
        e.SuppressKeyPress = true;
    }
}

关注点

请注意,`ProcessKeyPreview` 可以返回 `true` 或 `false` 以指示密钥是否已被处理。但是,如果您没有处理它,您应该推迟到基本的 `ProcessKeyPreview` 方法。

注意:`ModifierKeys` 是基础 `Control` 类的一部分,并返回一个值,指示哪个修饰键(SHIFT、CTRL 和 ALT)处于按下状态。 您可以像往常一样在 `KeyEventArgs` 中设置它...

历史

  • 2008 年 1 月 2 日
    • 添加了处理,如果 `KeyDown` 事件中设置了 `e.SuppressKeyPress`,则删除消息
    • 将消息编号更改为常量,使其更易于阅读
  • 2008 年 2 月 7 日
    • 删除了对 Form 的引用以避免混淆,并提到了 `KeyPreview`
© . All rights reserved.