如何在 wpf C# 中正确创建全局鼠标热键
How properly create a global mouse hotkey in wpf C#
我正在尝试使用 SetWindowsHookEx()
在我的程序中创建全局鼠标热键。到目前为止,我已经尝试像我在这里看到的 post 一样创建它们,但是,我真的不知道如何完成它。
目前的问题是我不完全知道 _globalMouseHookCallback 是什么。
这是我到目前为止写的:
class GlobalHotkey
{
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private IntPtr _hGlobalMouseHook;
MainWindow _m;
private const int WH_KEYBOARD_LL = 13;
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
private static IntPtr hook = IntPtr.Zero;
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public void SetUpHook()
{
_m.rtbLog.AppendText("Setting up global Hotkey \n");
_globalMouseHookCallback = LowLevelMouseProc;
_hGlobalMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _globalMouseHookCallback, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
if (_hGlobalMouseHook == IntPtr.Zero)
{
_m.rtbLog.AppendText("Unable to set up global mouse hook\n");
}
}
public void ClearHook()
{
_m.rtbLog.AppendText("Deleting global mouse hook\n");
if (_hGlobalMouseHook != IntPtr.Zero)
{
if (!UnhookWindowsHookEx(_hGlobalMouseHook))
{
_m.rtbLog.AppendText("Unable to delete global mouse hook\n");
}
_hGlobalMouseHook = IntPtr.Zero;
}
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var wmMouse = wParam;
if (wmMouse == (IntPtr)WM_LBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_LBUTTONUP)
{
_m.rtbLog.AppendText("Left Mouse up");
}
if (wmMouse == (IntPtr)WM_RBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_RBUTTONUP)
{
_m.rtbLog.AppendText("Right Mouse up");
}
}
return CallNextHookEx(_hGlobalMouseHook, nCode, wParam, lParam);
}
}
这个全局热键的东西非常难,有没有像我这样的新手可以轻松解释它的教程:P?
编辑
所以我尝试将 Koby Ducks 示例改编为我的代码。
这是我的热键class:
class GlobalHotkey
{
MainWindow _m;
private static readonly object sSyncObj = new object();
private static readonly HashSet<Key> sDownKeys = new HashSet<Key>();
private static readonly Dictionary<Key, Action> sPressActions = new Dictionary<Key, Action>();
private static readonly Dictionary<Key, Action> sReleaseActions = new Dictionary<Key, Action>();
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public static void ProcessKeyDown(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (!sDownKeys.Contains(key))
{
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void ProcessKeyUp(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (sDownKeys.Remove(key))
{
if (sReleaseActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void AttachPressAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(Key key)
{
lock (sSyncObj)
{
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(Key key)
{
lock (sSyncObj)
{
return sReleaseActions.Remove(key);
}
}
}
然后我创建了我的动作
public void MyTestAction()
{
rtbLog.AppendText("The B key was pressed");
}
myAction = new Action(MyTestAction);
但是当我将我的事件处理程序添加到 PreviewKeyUp- 和 Down 事件时,它给我一个错误,指出 ProcessKeyUp- 和 Down 的参数与 PreviewKeyUp- 和 Down 不同。
PreviewKeyDown += GlobalHotkey.ProcessKeyDown;
PreviewKeyUp += GlobalHotkey.ProcessKeyUp;
编辑: 无论当前焦点如何 window(又名 "global"),请参阅 this answer on the Win32 keyboard API.
对于在您的应用处于焦点状态时的输入(也称为 "local"),您可以使用预览事件。
public static class HotKeySystem
{
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (!sDownKeys.Contains(key)) {
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void ProcessKeyUp(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (sDownKeys.Remove(key)) {
if (sReleaseActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void AttachPressAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(KeyCode key)
{
lock (sSyncObj) {
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(KeyCode key)
{
lock (sSyncObj) {
return sReleaseActions.Remove(key);
}
}
private static readonly object sSyncObj = new object();
// The keys that are currently down.
private static readonly HashSet<KeyCode> sDownKeys = new HashSet<KeyCode>();
// Actions triggered when a key was up, but is now down.
private static readonly Dictionary<KeyCode, Action> sPressActions = new Dictionary<KeyCode, Action>();
// Actions triggered when a key was down, but is now up.
private static readonly Dictionary<KeyCode, Action> sReleaseActions = new Dictionary<KeyCode, Action>();
}
// When possible, subclass your windows from this to automatically add hotkey support.
public class HotKeyWindow : Window
{
protected override void OnPreviewKeyDown(KeyEventArgs args)
{
HotKeySystem.ProcessKeyDown(this, args);
base.OnPreviewKeyDown(args);
}
protected override void OnPreviewKeyUp(KeyEventArgs args)
{
HotKeySystem.ProcessKeyUp(this, args);
base.OnPreviewKeyUp(args);
}
}
// When not possible, attach event handlers like this:
window.PreviewKeyDown += HotKeySystem.ProcessKeyDown;
window.PreviewKeyUp += HotKeySystem.ProcessKeyUp;
// Use it like this:
HotKeySystem.AttachPressAction(KeyCode.F1, () => {
// F1 hotkey functionality.
});
无论您使用的是此方法还是 Win32 API,请考虑其中的含义。如果您绑定了 'A',那么您将无法在文本输入控件中输入 'a' 或 'A'。解决此问题的一种方法是:
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
// Detect keyboard input controls you may have issues with.
// If one has keyboard focus, skip hotkey processing.
if (Keyboard.FocusedElement is TextBox) {
return;
}
// ...
}
我正在尝试使用 SetWindowsHookEx()
在我的程序中创建全局鼠标热键。到目前为止,我已经尝试像我在这里看到的 post 一样创建它们,但是,我真的不知道如何完成它。
目前的问题是我不完全知道 _globalMouseHookCallback 是什么。
这是我到目前为止写的:
class GlobalHotkey
{
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private IntPtr _hGlobalMouseHook;
MainWindow _m;
private const int WH_KEYBOARD_LL = 13;
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
private static IntPtr hook = IntPtr.Zero;
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public void SetUpHook()
{
_m.rtbLog.AppendText("Setting up global Hotkey \n");
_globalMouseHookCallback = LowLevelMouseProc;
_hGlobalMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _globalMouseHookCallback, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
if (_hGlobalMouseHook == IntPtr.Zero)
{
_m.rtbLog.AppendText("Unable to set up global mouse hook\n");
}
}
public void ClearHook()
{
_m.rtbLog.AppendText("Deleting global mouse hook\n");
if (_hGlobalMouseHook != IntPtr.Zero)
{
if (!UnhookWindowsHookEx(_hGlobalMouseHook))
{
_m.rtbLog.AppendText("Unable to delete global mouse hook\n");
}
_hGlobalMouseHook = IntPtr.Zero;
}
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var wmMouse = wParam;
if (wmMouse == (IntPtr)WM_LBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_LBUTTONUP)
{
_m.rtbLog.AppendText("Left Mouse up");
}
if (wmMouse == (IntPtr)WM_RBUTTONDOWN)
{
_m.rtbLog.AppendText("Right Mouse down");
}
if (wmMouse == (IntPtr)WM_RBUTTONUP)
{
_m.rtbLog.AppendText("Right Mouse up");
}
}
return CallNextHookEx(_hGlobalMouseHook, nCode, wParam, lParam);
}
}
这个全局热键的东西非常难,有没有像我这样的新手可以轻松解释它的教程:P?
编辑 所以我尝试将 Koby Ducks 示例改编为我的代码。
这是我的热键class:
class GlobalHotkey
{
MainWindow _m;
private static readonly object sSyncObj = new object();
private static readonly HashSet<Key> sDownKeys = new HashSet<Key>();
private static readonly Dictionary<Key, Action> sPressActions = new Dictionary<Key, Action>();
private static readonly Dictionary<Key, Action> sReleaseActions = new Dictionary<Key, Action>();
public GlobalHotkey(MainWindow m)
{
_m = m;
}
public static void ProcessKeyDown(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (!sDownKeys.Contains(key))
{
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void ProcessKeyUp(KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj)
{
if (sDownKeys.Remove(key))
{
if (sReleaseActions.TryGetValue(key, out action))
{
args.Handled = true;
}
}
}
action.Invoke();
}
public static void AttachPressAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(Key key)
{
lock (sSyncObj)
{
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(Key key, Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj)
{
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(Key key)
{
lock (sSyncObj)
{
return sReleaseActions.Remove(key);
}
}
}
然后我创建了我的动作
public void MyTestAction()
{
rtbLog.AppendText("The B key was pressed");
}
myAction = new Action(MyTestAction);
但是当我将我的事件处理程序添加到 PreviewKeyUp- 和 Down 事件时,它给我一个错误,指出 ProcessKeyUp- 和 Down 的参数与 PreviewKeyUp- 和 Down 不同。
PreviewKeyDown += GlobalHotkey.ProcessKeyDown;
PreviewKeyUp += GlobalHotkey.ProcessKeyUp;
编辑: 无论当前焦点如何 window(又名 "global"),请参阅 this answer on the Win32 keyboard API.
对于在您的应用处于焦点状态时的输入(也称为 "local"),您可以使用预览事件。
public static class HotKeySystem
{
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (!sDownKeys.Contains(key)) {
sDownKeys.Add(key);
if (sPressActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void ProcessKeyUp(object sender, KeyEventArgs args)
{
var key = args.Key;
var action = default(Action);
lock (sSyncObj) {
if (sDownKeys.Remove(key)) {
if (sReleaseActions.TryGetValue(key, out action)) {
args.Handled = true;
}
}
}
// Invoke outside of the lock.
action?.Invoke();
}
public static void AttachPressAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sPressActions.Add(key, action);
}
}
public static bool DetachPressAction(KeyCode key)
{
lock (sSyncObj) {
return sPressActions.Remove(key);
}
}
public static void AttachReleaseAction(KeyCode key, Action action)
{
if (action == null) {
throw new ArgumentNullException(nameof(action));
}
lock (sSyncObj) {
sReleaseActions.Add(key, action);
}
}
public static bool DetachReleaseAction(KeyCode key)
{
lock (sSyncObj) {
return sReleaseActions.Remove(key);
}
}
private static readonly object sSyncObj = new object();
// The keys that are currently down.
private static readonly HashSet<KeyCode> sDownKeys = new HashSet<KeyCode>();
// Actions triggered when a key was up, but is now down.
private static readonly Dictionary<KeyCode, Action> sPressActions = new Dictionary<KeyCode, Action>();
// Actions triggered when a key was down, but is now up.
private static readonly Dictionary<KeyCode, Action> sReleaseActions = new Dictionary<KeyCode, Action>();
}
// When possible, subclass your windows from this to automatically add hotkey support.
public class HotKeyWindow : Window
{
protected override void OnPreviewKeyDown(KeyEventArgs args)
{
HotKeySystem.ProcessKeyDown(this, args);
base.OnPreviewKeyDown(args);
}
protected override void OnPreviewKeyUp(KeyEventArgs args)
{
HotKeySystem.ProcessKeyUp(this, args);
base.OnPreviewKeyUp(args);
}
}
// When not possible, attach event handlers like this:
window.PreviewKeyDown += HotKeySystem.ProcessKeyDown;
window.PreviewKeyUp += HotKeySystem.ProcessKeyUp;
// Use it like this:
HotKeySystem.AttachPressAction(KeyCode.F1, () => {
// F1 hotkey functionality.
});
无论您使用的是此方法还是 Win32 API,请考虑其中的含义。如果您绑定了 'A',那么您将无法在文本输入控件中输入 'a' 或 'A'。解决此问题的一种方法是:
public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
// Detect keyboard input controls you may have issues with.
// If one has keyboard focus, skip hotkey processing.
if (Keyboard.FocusedElement is TextBox) {
return;
}
// ...
}