如何使LowLevelKeyboardProc/LowLevelMouseProc仅对聚焦应用生效而非全局?
Make LowLevelMouseProc Only Active for Target Focused Application
Got it, let's tweak your code so that your mouse hook only blocks clicks when your target app is the focused (foreground) window—no more global blocking! Here's how to do it step by step:
Core Idea
The solution boils down to three key steps:
- Define your target application (by process name or PID)
- In your hook callback, check if the current foreground window belongs to the target process
- Only intercept mouse events if the target app is active; otherwise, let the event pass through normally
Modified Implementation
We'll add new WinAPI calls to get the foreground window and its associated process ID, then update your MouseHook class to include a target process identifier and logic to validate the active window before blocking events. Here's the updated code with comments highlighting changes:
using System; using System.Runtime.InteropServices; using System.Diagnostics; namespace GlobalLowLevelHooks { public class MouseHook { public bool enabled = false; // Add this property to set your target app's process name (e.g., "notepad.exe") public string TargetProcessName { get; set; } public delegate IntPtr MouseHookHandler(int nCode, IntPtr wParam, IntPtr lParam); public MouseHookHandler hookHandler; public delegate void MouseHookCallback(MSLLHOOKSTRUCT mouseStruct); #region Events public event MouseHookCallback LeftButtonDown; public event MouseHookCallback LeftButtonUp; public event MouseHookCallback RightButtonDown; public event MouseHookCallback RightButtonUp; public event MouseHookCallback MouseMove; public event MouseHookCallback MouseWheel; public event MouseHookCallback DoubleClick; public event MouseHookCallback MiddleButtonDown; public event MouseHookCallback MiddleButtonUp; #endregion public IntPtr hookID = IntPtr.Zero; public void Enable() { hookHandler = HookFunc; hookID = SetHook(hookHandler); enabled = true; } public void Disable() { if (hookID == IntPtr.Zero) return; UnhookWindowsHookEx(hookID); hookID = IntPtr.Zero; enabled = false; } ~MouseHook() { Disable(); // Fixed typo here—was Enable(), should clean up the hook instead } private IntPtr SetHook(MouseHookHandler proc) { using (ProcessModule module = Process.GetCurrentProcess().MainModule) return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(module.ModuleName), 0); } private IntPtr HookFunc(int nCode, IntPtr wParam, IntPtr lParam) { // Only process events if hook is valid and target is set if (nCode >= 0 && !string.IsNullOrEmpty(TargetProcessName)) { // Check if the focused window belongs to our target app if (IsTargetAppActive()) { // Block mouse clicks only when target is active MouseMessages message = (MouseMessages)wParam; if (message is MouseMessages.WM_LBUTTONDOWN or MouseMessages.WM_RBUTTONDOWN or MouseMessages.WM_MBUTTONDOWN or MouseMessages.WM_LBUTTONDBLCLK) { return (IntPtr)1; // Block the event from reaching the app } } } // Pass event through if target isn't active or hook is invalid return CallNextHookEx(hookID, nCode, wParam, lParam); } // Helper method to verify if target app is in focus private bool IsTargetAppActive() { IntPtr foregroundWindow = GetForegroundWindow(); if (foregroundWindow == IntPtr.Zero) return false; GetWindowThreadProcessId(foregroundWindow, out uint processId); if (processId == 0) return false; try { using (Process process = Process.GetProcessById((int)processId)) { // Match process name (ignore .exe extension case) return process.ProcessName.Equals( TargetProcessName.TrimEnd('.', 'e', 'x', 'e'), StringComparison.OrdinalIgnoreCase); } } catch (ArgumentException) { // Process might have exited since we checked return false; } } #region WinAPI private const int WH_MOUSE_LL = 14; private enum MouseMessages { WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202, WM_MOUSEMOVE = 0x0200, WM_MOUSEWHEEL = 0x020A, WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205, WM_LBUTTONDBLCLK = 0x0203, WM_MBUTTONDOWN = 0x0207, WM_MBUTTONUP = 0x0208 } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x; public int y; } [StructLayout(LayoutKind.Sequential)] public struct MSLLHOOKSTRUCT { public POINT pt; public uint mouseData; public uint flags; public uint time; public IntPtr dwExtraInfo; } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, MouseHookHandler lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public 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); // Added WinAPI functions to get foreground window and its process ID [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); #endregion } }
Key Changes Breakdown
- TargetProcessName Property: Lets you specify which app to target (e.g.,
"notepad.exe"or just"notepad"—the helper method handles both formats). - IsTargetAppActive() Helper: Uses
GetForegroundWindow()to grab the current focused window, thenGetWindowThreadProcessId()to get its PID, and checks if the process name matches your target. - Hook Logic Update: Only blocks mouse events if the target app is active. If not, it calls
CallNextHookExto let the event proceed to whatever app is focused. - Fixed Destructor: Changed
Enable()toDisable()in the finalizer to properly clean up the hook when the object is disposed.
How to Use It
Initialize the hook and set your target before enabling it:
var mouseHook = new MouseHook(); mouseHook.TargetProcessName = "your-target-app.exe"; // Replace with your app's process name mouseHook.Enable();
Quick Notes
- If you need to target a specific instance of an app (instead of all instances), swap the
TargetProcessNameproperty for aTargetProcessIdproperty and adjust theIsTargetAppActive()method to compare PIDs directly. - Low-level hooks run in your app's context, so make sure your app stays running for the hook to work.
内容的提问来源于stack exchange,提问作者Mark Aven
相关产品推荐
相关产品推荐

