VSTO Windows Hook keydown事件被称为10次

所以,我一直在开发一个类来处理VSTO插件中的Kwyboardinput,到目前为止我一直使用Windows钩子来取得相对的成功。

有这样的代码:

//..... private const int WH_KEYBOARD = 2; private const int WH_MOUSE = 7; private enum WM : uint { KEYDOWN = 0x0100, KEYFIRST = 0x0100, KEYLAST = 0x0108, KEYUP = 0x0101, MOUSELEFTDBLCLICK = 0x0203, MOUSELEFTBTNDOWN = 0x0201, MOUSELEFTBTNUP = 0x0202, MOUSEMIDDBLCLICK = 0x0209, MOUSEMIDBTNDOWN = 0x0207, MOUSEMIDBTNUP = 0x0208, MOUSERIGHTDBLCLK = 0x0206, MOUSERIGHTBTNDOWN = 0x0204, MOUSERIGHTBTNUP = 0x0205 } private hookProcedure proc; private static IntPtr hookID = IntPtr.Zero; //Enganches [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern bool unHookWindowsHookEx(int hookId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string name); [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetCurrentThreadId(); public CPInputListener() { proc = keyBoardCallback; hookID = setHook(proc); } private IntPtr setHook(hookProcedure procedure){ ProcessModule module = Process.GetCurrentProcess().MainModule; uint threadId = (uint)GetCurrentThreadId(); return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId); } public void stopListeningAll() { unHookWindowsHookEx(WH_KEYBOARD);//For now } private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) { if (ncode >= 0) { //LPARAM pretty useless Keys key = (Keys)wParam; KeyEventArgs args = new KeyEventArgs(key); onKeyDown(args);//for now } return CallNextHookEx(hookID, ncode, wParam, lParam); } //.... 

我成功地接受了键盘input,但是这里是一个很大的神秘莫测的地方; 每按一次键,不pipe速度如何快,事件(onKeyDown)都被精确地调用10次,不会less于这个次数。

如果按键被长时间按下,则该事件将被保持被调用,而不是仅调用一次而是10次10​​次。

到目前为止我已经尝试过

  1. 使用wParam在Key Up上调用所需的事件:似乎没有工作,在我看到的所有代码处理Key down和up事件,使用IntPtr wParam ,但是从该variables我只能检索键码没有帮助。
  2. 使用lParamnCode :这些variables给这10个调用之间的不一致的值, ncode倾向于检索0和3和lParam一些值似乎是非托pipe内存地址…

我期望什么

我希望onKeyDown被调用一次,当按下按键或另一方面可以通过调用方法的关键,我希望被称为只是每个键释放一次。

如何绕过这个

如果我找不到合理的答案,我正在考虑使用定制的定时器来丢弃所有这些调用,只使用最后一个调用,如果一切都失败了,你会推荐这个吗?

非常感谢! 快乐,善良! :d

首先,您必须筛选正确的ncode才能获得您应该处理的按键。 (例如,您不应该处理HC_NOREMOVE 。)
然后,你必须检查是否是在lParam使用标志的KeyDownKeyUp事件。

如果按键被长时间按下,多个KeyDown事件已经被Win32合并为一个调用,所以你不必在这里做任何特殊的事情。 但是如果你只想得到最后一个KeyUp事件,那么你必须在lParam检查另一个标志。

所以,这是你需要改变的代码:

 private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) { // Feel free to move the const to a private field. const int HC_ACTION = 0; if (ncode == HC_ACTION) { Keys key = (Keys)wParam; KeyEventArgs args = new KeyEventArgs(key); bool isKeyDown = ((ulong)lParam & 0x40000000) == 0; if (isKeyDown) onKeyDown(args); else { bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000; if (isLastKeyUp) onKeyUp(args); } } return CallNextHookEx(hookID, ncode, wParam, lParam); } 

在评论中按要求编辑:
不幸的是,这些参数的文档相当稀疏。

一个“提示”不处理任何其他的HC_ACTION可以在这里找到,说:

 if (nCode < 0) // do not process message return ...; // ... switch (nCode) { case HC_ACTION: // ... do something ... break; default: break; } // ... return CallNextHookEx(...); 

另一个支持声明在这里:
为什么我的键盘钩子多次收到相同的键盘键和键盘键?

lParam的内容定义如下 :

 typedef struct tagKBDLLHOOKSTRUCT { DWORD vkCode; DWORD scanCode; DWORD flags; DWORD time; ULONG_PTR dwExtraInfo; } 

(就像提醒一下: DWORD在x86上以及在x64平台上的大小是4字节 )。

lParam flags的文档可以在这里和这里找到。
在这个链接中描述了这一点

  • 位30(= 0x40000000 )是以前的键状态
    (如果密钥关闭,则为1如果密钥导致此调用的新密钥状态之前 启动则为0
  • 位31(= 0x80000000 )是过渡状态
    (按键上的0和键上的键1

前一个关键状态 ”这个词相当混乱,但实际上它恰好与现状相反(因为只有上升或下降,没有第三种状态)。

当“ 键盘的自动重复功能 ”被激活时,即当按下足够长的时间时,过渡状态尤其相关。

另一个示例(使用VC7)可以在这里找到:

 if (HIWORD (lParam) & 0xC000) // Key up without autorepeat else // Key down 

其中0xC000只是0x4000 || 0x8000 0x4000 || 0x8000并且定义了密钥被释放并且已经创建了一个密钥注册事件。

总而言之,令人困惑,但仍是如此。
也许还有其他的链接可以更好地描述这种情况,但是我想像这样的时候,像微软沙盒(如UWP)中的新应用程序开发“应该完成”,并且VSTO正在为其让路用HTML和JavaScript编写的更新的Office加载项 ,没有人关心低层次的钩子了。