后台应用程序监听键盘活动






4.80/5 (30投票s)
捕获键盘事件的类。
引言
此处提供的 KeyboardListener
类允许捕获键盘事件,即使有其他应用程序在前台运行。您的应用程序中可能有多个线程并行监听事件,每个线程以自己的方式处理事件。
如果您只是想为您的应用程序添加热键支持,您也可以查看 CodeProject 文章 “.NET 系统范围热键组件”。但是,如果您对在应用程序中处理键盘事件有其他计划,那么这个 KeyboardListener
类可能就是您想要的。
KeyboardListener
类只能在 Windows XP 上使用,因为它的实现依赖于 Win32 方法 RegisterRawInputDevices
和 GetRawInputData
,而这些方法目前只在 XP 上可用。
使用代码
1. 演示应用程序
让我们从简单的部分开始:在应用程序中使用该类。演示应用程序是一个纯 .NET 应用程序,只包含一个窗体,该窗体又只包含一个文本标签(参见演示项目)。Form1_Load
方法在窗体加载时调用,它只向 KeyboardListener
类导出的静态 s_KeyEventHandler
事件添加一个新的 EventHandler
。当然,您可以自由地在应用程序的多个位置添加多个 EventHandler
实例。
private void Form1_Load(object sender, System.EventArgs e)
{
// Watch for keyboard activity
KeyboardListener.s_KeyEventHandler += new
EventHandler(KeyboardListener_s_KeyEventHandler);
}
传递给 EventHandler
构造函数的 KeyboardListener_s_KeyEventHandler
方法会分析参数,用一些按键信息更新窗体上的文本标签 lblKeyPressed
,并根据按键动作改变窗体的背景颜色:当按下按键时,背景变为红色;当释放按键时,背景再次变为绿色。
private void KeyboardListener_s_KeyEventHandler(object sender, EventArgs e)
{
KeyboardListener.UniversalKeyEventArgs eventArgs =
(KeyboardListener.UniversalKeyEventArgs)e;
lblKeyPressed.Text = string.Format("Key = {0} Msg = {1} Text = {2}",
eventArgs.m_Key, eventArgs.m_Msg, eventArgs.KeyData);
// 256 : key down 257 : key up
if(eventArgs.m_Msg == 256)
{
this.BackColor = Color.Red;
}
else
{
this.BackColor = Color.Green;
}
}
2. KeyboardListener 类
KeyboardListener
类由三个重要部分组成。首先,有前面提到的静态 EventHandler
变量 s_KeyEventHandler
。任何对接收键盘事件感兴趣的应用程序线程都可以订阅此事件。
public static event EventHandler s_KeyEventHandler;
其次,是静态 KeyHandler
方法,当发生键盘事件时会调用它。它会遍历 s_KeyEventHandler
中包含的所有 EventHandler
实例,并用一个 UniversalKeyEventArgs
实例调用每个实例,该实例包含键盘事件提供的 **按键** 和 **消息**。遍历所有委托的优点是,通过在循环中添加 try
/catch
块,一个 EventHandler
可能会失败,但所有其他 EventHandler
实例仍然会被调用。换句话说,一个订阅者失败不会阻止其他订阅者被调用。
private static void KeyHandler(ushort key, uint msg)
{
if(s_KeyEventHandler != null)
{
Delegate[] delegates = s_KeyEventHandler.GetInvocationList();
foreach(Delegate del in delegates)
{
EventHandler sink = (EventHandler)del;
try
{
// This is a static class, therefore null
// is passed as the object reference
sink(null,new UniversalKeyEventArgs(key,msg));
}
// You can add some meaningful code to this catch block
catch{};
}
}
}
最后是 ListeningWindow
类。顾名思义,此类的一个实例用于监听。它监听键盘事件的发生。实际上,这个类只有一个实例。这个实例在调用 KeyboardListener
类的静态构造函数时创建。查看下面的 ListeningWindow
构造函数,您会发现它在构造时需要一个委托。这个委托恰好是上面提到的 KeyHandler
。因此,在应用程序运行时,每当 ListeningWindow
检测到键盘事件时,它就会调用 KeyHandler
委托,然后该委托会调用所有已安装的 EventHandler
。
public ListeningWindow(KeyDelegate keyHandlerFunction) { m_KeyHandler = keyHandlerFunction; CreateParams cp = new CreateParams(); // Fill in the CreateParams details. cp.Caption = "Hidden window"; cp.ClassName = null; cp.X = 0x7FFFFFFF; cp.Y = 0x7FFFFFFF; cp.Height = 0; cp.Width = 0; cp.Style = WS_CLIPCHILDREN; // Create the actual invisible window this.CreateHandle(cp); // Register for Keyboard notification unsafe { try { RAWINPUTDEV myRawDevice = new RAWINPUTDEV(); myRawDevice.usUsagePage = 0x01; myRawDevice.usUsage = 0x06; myRawDevice.dwFlags = RIDEV_INPUTSINK; myRawDevice.hwndTarget = this.Handle.ToPointer(); if (RegisterRawInputDevices(&myRawDevice, 1, (uint)sizeof(RAWINPUTDEV)) == false) { int err = Marshal.GetLastWin32Error(); throw new Win32Exception(err, "ListeningWindow::RegisterRawInputDevices"); } } catch {throw;} } }
ListeningWindow
类是 NativeWindow
类的子类,它只包含两个有趣的定义:构造函数和 WndProc
方法。
在 ListeningWindow
构造函数中,会创建一个不可见的窗口,然后将其 *注册* 以接收键盘输入消息。这些消息由 WndProc
方法分析,该方法重写了 NativeWindow
基类的方法。每当 WndProc
方法收到键盘消息时,它会检查该消息是否与上一条消息不同,如果不同,则调用在 ListeningWindow
构造函数中安装的委托。
protected override void WndProc(ref Message m) { ... receivedBytes = (uint)GetRawInputData((RAWINPUTHKEYBOARD*)(m.LParam.ToPointer()), RID_INPUT, lpb, &dwSize, sizeof_RAWINPUTHEADER); if ( receivedBytes == dwSize ) { RAWINPUTHKEYBOARD* keybData = (RAWINPUTHKEYBOARD*)lpb; // Finally, analyze the data if(keybData->header.dwType == RIM_TYPEKEYBOARD) { if((m_PrevControlKey != keybData->VKey) || (m_PrevMessage != keybData->Message)) { m_PrevControlKey = keybData->VKey; m_PrevMessage = keybData->Message; // Call the delegate in case data satisfies m_KeyHandler(keybData->VKey,keybData->Message); } } } ... }
使用 PInvoke
KeyboardListener
类实现的关键是 **RegisterRawInputDevices
** 和 **GetRawInputData
** 这两个方法。它们能够捕获和处理后台的键盘事件。由于 .NET 不支持这些调用,因此它们是通过 PInvoke 机制调用的。
[DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)]
[return : MarshalAs(UnmanagedType.Bool)]
internal static extern unsafe bool
RegisterRawInputDevices( RAWINPUTDEV* rawInputDevices,
uint numDevices, uint size);
[DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)]
[return : MarshalAs(UnmanagedType.I4)]
internal static extern unsafe int GetRawInputData( void* hRawInput,
uint uiCommand, byte* pData, uint* pcbSize, uint cbSizeHeader);
未来构想
UniversalKeyEventArgs
是EventArgs
类的子类,您可能会问为什么需要它。老实说,没有它也可以。但那时您必须组装正确的Keys
实例作为参数传递给EventArgs
构造函数。在本次发布中,我选择了一种简单的方法,这肯定可以在未来的版本中得到改进。- 使用相同的技术,可以创建一个
MouseListener
类。请参阅我的下一篇文章。
历史
- 2005 年 1 月 20 日,版本 1.0.0,初始发布。