[英]How to stop further processing global hotkeys in C#
我正在嘗試創建一個對全局熱鍵做出反應的 C# 應用程序; ALT+H
然后當我松開ALT
鍵時。 這實際上工作得很好,但我的問題是,當我的應用程序完成了它應該對熱鍵執行的任何操作時,它應該阻止其他應用程序處理該熱鍵。 我從 StackOverflow 帖子中獲得了下面的大部分代碼,C# 應用程序中的全局鍵盤捕獲,但我在其他地方看到了return (IntPtr)1;
應該能夠停止對密鑰的進一步處理。
但是...當我在 Word 或 Wordpad 中並按ALT+H
然后 Word 會顯示所有類型的菜單 - 我不希望它顯示,因為我只希望我的應用程序執行某些操作:-)
我很抱歉這段相當長的代碼,但我認為它很重要,因此您可以獲得完整的概述。 我使用return (IntPtr)1;
地方只有 1 return (IntPtr)1;
.
我的代碼:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Input;
namespace MyNewProgram
{
static class Program
{
// Enable hotkeys
// https://stackoverflow.com/a/604417/2028935
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYUP = 0x0105;
private const int VK_SHIFT = 0x10;
private const int VK_MENU = 0x12;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
// Other variables
public static bool isAltPressedInThisApp = false;
private static MainWindow MyMainWindow;
// --------------------------------------------------------------------------------------
[STAThread] // STAThreadAttribute indicates that the COM threading model for the application is single-threaded apartment, https://stackoverflow.com/a/1361048/2028935
static void Main()
{
// Hook application to keyboard
_hookID = SetHook(_proc);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MyMainWindow = new MainWindow();
Application.Run(MyMainWindow);
// Unhook application from keyboard
UnhookWindowsHookEx(_hookID);
}
// --------------------------------------------------------------------------------------
// Required functions for globally hooking the keyboard
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern short GetKeyState(int keyCode);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
// --------------------------------------------------------------------------------------
// Hook the keyboard - action
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
// React on KEYDOWN
if (nCode >= 0 && ((wParam == (IntPtr)WM_KEYDOWN) || (wParam == (IntPtr)WM_SYSKEYUP)))
{
int vkCode = Marshal.ReadInt32(lParam);
// H
if ((Keys)vkCode == Keys.H)
{
isAltPressedInThisApp = true;
// Is ALT pressed down
if ((GetKeyState(VK_MENU) & 0x8000) != 0)
{
Console.WriteLine("ALT + H");
return (IntPtr)1; // do not allow others to hook this key combo
}
}
}
// React on KEYUP
if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
// Is ALT not pressed down
if ((Keys)vkCode == Keys.LMenu)
{
Console.WriteLine("ALT UP");
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
// --------------------------------------------------------------------------------------
// Hook the keyboard
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
}
}
我不是一個可靠的 C# 開發人員,因為我在這里開始了我的前幾個小步 - 那么我為什么要把自己投入全局熱鍵的深水作為第一件事;-) 有沒有人可以在這里給出一些提示可能在某處有一個愚蠢的錯誤?
背后的想法是,每當按下 alt 鍵時,它就會開始將鍵吞入一個本地列表中。 如果我們需要看到的模式已經被看到,它就不會發送,但對於其他模式,它會按照收到的順序再次發送密鑰。
考試階段代碼:
enum WM
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYUP = 0x0105,
WM_SYSKEYDOWN = 0x0104,
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Enum.TryParse<Keys>($"{kbd.vkCode}", out Keys key);
Enum.TryParse<WM>($"{wParam}", out WM message);
Console.WriteLine($"{message}:{key}");
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
當按下alt+h並釋放時,輸出:
WM_SYSKEYDOWN:LMenu
WM_SYSKEYDOWN:H
WM_SYSKEYUP:H
WM_KEYUP:LMenu
如您所見,Windows 發送了 alt 和 h 鍵。 您提供的代碼僅捕獲 H 鍵,因此接收鍵盤消息的窗口認為按下了 alt。
沒有人可以獲取和過濾掉以前按下的鍵,所以我們需要在看到 alt 鍵被按下時捕獲它。 如果下一個密鑰不是 H,我們應該按照我們收到的順序發送我們獲取的密鑰。
我寫了下面的代碼來處理這種情況,但我不能確定它在真正的 Windows 機器上是如何工作的,因為我有一個 osx 操作系統,因為我在虛擬機中運行 Windows,這也是更改密鑰當我按下時中風。
如果還不夠,你可以等一下,我也許可以在我的辦公室用真正的 Windows 機器嘗試解決這個問題。 但我認為你明白了這個想法並自己解決了。
[StructLayout(LayoutKind.Sequential)]
public class KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public KBDLLHOOKSTRUCTFlags flags;
public uint time;
public UIntPtr dwExtraInfo;
}
[Flags]
public enum KBDLLHOOKSTRUCTFlags : uint
{
LLKHF_EXTENDED = 0x01,
LLKHF_INJECTED = 0x10,
LLKHF_ALTDOWN = 0x20,
LLKHF_UP = 0x80,
}
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
enum WM
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYUP = 0x0105,
WM_SYSKEYDOWN = 0x0104,
}
private static void ReSendKeys(int index = -1)
{
_index = -1;
var copiedKeys = _swallowedKeys.ToArray();
for (int i = 0; i < copiedKeys.Length; ++i)
{
bool up = copiedKeys[i].Item1 == (IntPtr)WM.WM_SYSKEYUP || copiedKeys[i].Item1 == (IntPtr)WM.WM_KEYUP;
keybd_event((byte)copiedKeys[i].Item2.vkCode, (byte)copiedKeys[i].Item2.scanCode, up ? 2u : 0u, UIntPtr.Zero);
}
_index = index;
_swallowedKeys.Clear();
}
private static List<Tuple<IntPtr, KBDLLHOOKSTRUCT>> _swallowedKeys = new List<Tuple<IntPtr, KBDLLHOOKSTRUCT>>();
private static int _index = 0;
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Enum.TryParse<Keys>($"{kbd.vkCode}", out Keys key);
Enum.TryParse<WM>($"{wParam}", out WM message);
Console.Write($"{message}:{key}");
// we know that when _index is -1, ReSendKeys function has been called
// so do not filter out first alt key
if (_index == -1)
{
_index++;
Console.WriteLine();
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
// we are at the beginning of the sequence we will catch
// if it's alt key filter it out, and increment the variable
if (_index == 0)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_SYSKEYDOWN && key == Keys.LMenu)
{
_index++;
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
else
{
_swallowedKeys.Clear();
// do nothing
}
}
if (_index == 1)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
// if the next key is H, then filter it out also
if (message == WM.WM_SYSKEYDOWN && key == Keys.H)
{
_index++;
_swallowedKeys.RemoveAt(_swallowedKeys.Count - 1);
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
// if not, we filtered out wrong sequence, we need to resend them
else
{
Console.WriteLine();
ReSendKeys();
return (IntPtr)1;
}
}
if (_index == 2)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_SYSKEYUP && key == Keys.H)
{
_index++;
_swallowedKeys.RemoveAt(_swallowedKeys.Count - 1);
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
else
{
// if user pressed H but not released and pressed another key at the same time
// i will pass that situation, if u need to handle something like that, you got the idea, please fill that block of code
}
}
if (_index == 3)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_KEYUP && key == Keys.LMenu)
{
_index = 0;
_swallowedKeys.Clear();
Console.WriteLine(" filtered out");
Console.WriteLine("shortcut disabled");
return (IntPtr)1;
}
else
{
Console.WriteLine();
// user has been pressed Alt + H, H released but not alt, so we can expect one H again, but we need to send this pressed key with Alt prefixed
ReSendKeys(1);
return (IntPtr)1;
}
}
Console.WriteLine();
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.