c# - 每次击键都会调用 LowLevelKeyboardProc 两次
问题描述
我尝试按照此处所述执行 VSTO 密钥挂钩:Excel VSTO 密钥挂钩
我使用 Alex Butenko 的答案创建了这个类,它(应该)在每次按下键时调用 OnKeyPress。问题是,当我按下一个键时, OnKeyPress 会被调用两次:
static class ShortcutManager
{
delegate int LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
static readonly LowLevelKeyboardProc _proc = HookCallback;
static IntPtr _hookID = IntPtr.Zero;
const int WH_KEYBOARD = 2;
const int HC_ACTION = 0;
const int WM_KEYDOWN = 0x0100;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern short GetKeyState(int nVirtKey);
static bool _keyHookingStarted;
public static void Start(IShortcutDistributor dist)
{
m_dist = dist;
if (!_keyHookingStarted)
{
#pragma warning disable 0618
_hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
_keyHookingStarted = true;
}
}
public static void Stop()
{
m_dist = null;
if (_keyHookingStarted)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
_keyHookingStarted = false;
}
}
static IShortcutDistributor m_dist = null;
static void OnKeyPress(uint keys)
{
var crtl = IsKeyDown(Keys.LControlKey) || IsKeyDown(Keys.RControlKey);
Debug.WriteLine("Keys: "+ keys.ToString()+ " Crtl: "+ crtl.ToString());
m_dist?.RaiseKeyPressedEvent(new KeyPressedEventArgs((Keys)keys, crtl));
}
static bool IsKeyDown(Keys keys)
{
return (GetKeyState((int)keys) & 0x8000) == 0x8000;
}
static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
if (nCode == HC_ACTION)
{
Debug.WriteLine("nCode: " + nCode.ToString() + " wParam:" + wParam.ToString());
OnKeyPress((uint)wParam);
}
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
因此,当我按 crtl+q 时,调试输出为:
nCode: 0 wParam:81
Keys: 81 Crtl: True
nCode: 0 wParam:81
Keys: 81 Crtl: True
按一下空格会产生以下调试输出:
nCode: 0 wParam:32
Keys: 32 Crtl: False
nCode: 0 wParam:32
Keys: 32 Crtl: False
微软文档https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx说:“wParam 是 WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN 或 WM_SYSKEYUP”,但在我的情况在两个电话中都是一样的。
那么,我做错了什么?我错过了什么吗?
解决方案
好的,似乎我发现了我的问题:
我混淆了 LowLevelKeyboardProc 和 KeyboardProc。
当我使用“SetWindowsHookEx(WH_KEYBOARD,...”时,函数是 KeyboardProc 而不是 LowLevelKeyboardProc。所以这是正确的微软文档:
https://msdn.microsoft.com/en-us/library/ms644984(v=vs.85).aspx
所以 lParam 是“击键消息标志”。这意味着标志 KF_REPEAT = 30 告诉我按键重复的频率。当我检查是否重复 = 0 时,我只是接到第一个电话。所以这是我的(希望是正确的)代码:
static class ShortcutManager
{
delegate int KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
static readonly KeyboardProc _proc = HookCallback;
static IntPtr _hookID = IntPtr.Zero;
const int WH_KEYBOARD = 2;
const int WH_KEYBOARD_LL = 13;
const int HC_ACTION = 0;
const int WM_KEYDOWN = 0x0100;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern short GetKeyState(int nVirtKey);
static bool _keyHookingStarted;
public static void Start(IShortcutDistributor dist)
{
m_dist = dist;
if (!_keyHookingStarted)
{
#pragma warning disable 0618
_hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
_keyHookingStarted = true;
}
}
public static void Stop()
{
m_dist = null;
if (_keyHookingStarted)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
_keyHookingStarted = false;
}
}
static IShortcutDistributor m_dist = null;
const int KF_REPEAT = 0x4000;
static void OnKeyPress(uint keys)
{
var crtl = IsKeyDown(Keys.LControlKey) || IsKeyDown(Keys.RControlKey);
Debug.WriteLine("Keys: "+ keys.ToString()+ " Crtl: "+ crtl.ToString());
m_dist?.RaiseKeyPressedEvent(new KeyPressedEventArgs((Keys)keys, crtl));
}
static bool IsKeyDown(Keys keys)
{
return (GetKeyState((int)keys) & 0x8000) == 0x8000;
}
static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
if (nCode == HC_ACTION)
{
var repeat = (HiWord(lParam) & KF_REPEAT);
Debug.WriteLine("nCode: " + nCode.ToString() + " wParam:" + wParam.ToString() + " repeat: "+ repeat.ToString());
if (repeat == 0)
{
OnKeyPress((uint)wParam);
}
}
return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private static ulong HiWord(IntPtr ptr)
{
if (((ulong)ptr & 0x80000000) == 0x80000000)
return ((ulong)ptr >> 16);
else
return ((ulong)ptr >> 16) & 0xffff;
}
}
推荐阅读
- android - 从 web 服务获取数据并绑定到 autotextview 而不冻结 UI
- android - 制作带有菜单图标的工具栏,如亚马逊应用程序
- wpf - 突出显示 DataGrid 中的空单元格
- php - 拉拉维尔 5.6。如何使用 html 锚点或其他标签切换语言?
- css - Vuetify - v-btn(按钮)的标签与横向边界重叠
- kentico - 如何找到跨 Kentico 实例使用表单的每个页面?
- java - 工具栏 findViewById 返回空指针
- node.js - 如何终止 npm 查询器提示并将控制权返回到主菜单/功能
- linux - GNU makefile shell 命令无法正常工作
- mysql - 如何检查MySQL中是否存在主键