能否无需SetForegroundWindow,向指定应用(副屏Firefox)使用SendKeys?
不激活窗口向副屏Firefox发送按键的解决方案
我明白你的需求——想给放在第二竖屏的Firefox发送按键(比如右键箭头),但又不想让Firefox跳出来变成活动窗口。先说说为什么你之前的两种方法都没能达到预期效果:
- 直接用
PostMessage发WM_KEYDOWN无效,是因为现代浏览器(包括Firefox)早就不依赖原始的WM_KEY系列消息处理键盘输入了,它们用的是更高级的输入模型,而且你传递的lParam参数也不符合要求(WM_KEYDOWN的lParam需要包含扫描码、重复计数等详细信息,不是填0就行的)。 SendKeys.SendWait必须配合SetForegroundWindow才能工作,是因为SendKeys本质是模拟全局键盘输入,只有当前激活的窗口才能接住这些输入。
下面给你两种可行的解决方案,按推荐程度排序:
方案一:用UI Automation API(更可靠,适合现代应用)
Windows的UI Automation框架是专门做UI自动化的,它能直接和应用的控件交互,不需要激活整个窗口,特别适合Firefox这种现代浏览器。
步骤:
- 在你的项目里添加两个引用:
UIAutomationClient和UIAutomationTypes(右键项目→添加→引用→程序集→框架里找)。 - 用下面的代码实现功能:
using System.Windows.Automation; using System.Diagnostics; using System.Windows.Input; private void SendRightKeyToFirefox() { // 找到Firefox进程 var firefoxProc = Process.GetProcessesByName("firefox").FirstOrDefault(); if (firefoxProc == null || firefoxProc.MainWindowHandle == IntPtr.Zero) return; // 获取Firefox窗口的Automation元素 var firefoxWindow = AutomationElement.FromHandle(firefoxProc.MainWindowHandle); if (firefoxWindow == null) return; // 定位到浏览器的内容区域(Firefox的内容控件类型是Document) var contentCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Document); var contentElement = firefoxWindow.FindFirst(TreeScope.Subtree, contentCondition); if (contentElement == null) return; // 发送右键箭头的按下和释放事件 if (contentElement.TryGetCurrentPattern(KeyPattern.Pattern, out var patternObj)) { var keyPattern = patternObj as KeyPattern; keyPattern?.KeyPress(Key.Right); keyPattern?.KeyRelease(Key.Right); } }
说明:
- 这个方法不需要激活Firefox窗口,只需要让它的内容控件获得焦点(UI Automation会帮你处理,不会让整个窗口跳出来)。
- 如果你要发送其他按键,把
Key.Right换成对应的Key枚举值就行。
方案二:用AttachThreadInput + SendInput API(更底层,兼容性好)
这个方法是通过绑定线程输入的方式,让当前线程的输入事件能被目标窗口接收,同时不会激活它。
示例代码:
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Forms; public class NonActivatingKeySender { // 导入需要的Windows API [DllImport("user32.dll")] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern uint GetCurrentThreadId(); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); // 定义输入结构体 [StructLayout(LayoutKind.Sequential)] private struct INPUT { public int type; public InputUnion u; } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { [FieldOffset(0)] public KEYBDINPUT ki; } [StructLayout(LayoutKind.Sequential)] private struct KEYBDINPUT { public short wVk; public short wScan; public int dwFlags; public int time; public IntPtr dwExtraInfo; } private const int INPUT_KEYBOARD = 1; private const int KEYEVENTF_KEYUP = 0x0002; // 发送按键的方法 public static void SendKeyToWindow(IntPtr hWnd, Keys key) { uint targetThreadId = GetWindowThreadProcessId(hWnd, out _); uint currentThreadId = GetCurrentThreadId(); IntPtr originalForeground = GetForegroundWindow(); try { // 绑定当前线程和目标窗口的线程 AttachThreadInput(currentThreadId, targetThreadId, true); // 构造按下和释放按键的输入数据 INPUT[] inputs = new INPUT[2]; // 按下按键 inputs[0].type = INPUT_KEYBOARD; inputs[0].u.ki.wVk = (short)key; inputs[0].u.ki.dwFlags = 0; // 释放按键 inputs[1].type = INPUT_KEYBOARD; inputs[1].u.ki.wVk = (short)key; inputs[1].u.ki.dwFlags = KEYEVENTF_KEYUP; // 发送输入 SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT))); } finally { // 解绑线程输入,恢复原来的激活窗口 AttachThreadInput(currentThreadId, targetThreadId, false); SetForegroundWindow(originalForeground); } } // 按钮点击事件里调用 private void button1_Click(object sender, EventArgs e) { var firefoxProc = Process.GetProcessesByName("firefox").FirstOrDefault(); if (firefoxProc != null && firefoxProc.MainWindowHandle != IntPtr.Zero) { SendKeyToWindow(firefoxProc.MainWindowHandle, Keys.Right); } } }
说明:
AttachThreadInput让当前线程和Firefox的线程共享输入状态,这样SendInput发送的按键会被Firefox接收,而不需要激活它。- 最后一定要恢复原来的激活窗口,避免干扰你当前正在操作的窗口。
内容的提问来源于stack exchange,提问作者Johnny Wind
相关产品推荐
相关产品推荐

