SendKeys.Send() 无反应主因是焦点不在目标窗口或运行于非交互式会话;它仅向前台活动窗口发送WM_KEYDOWN/WM_KEYUP消息,依赖UI线程,不模拟硬件事件。
SendKeys.Send() 为什么没反应?
多数情况下不是代码写错了,而是当前焦点不在目标窗口、或者程序运行在非交互式会话(比如 windows 服务、计划任务后台运行)里。sendkeys.send() 只作用于前台活动窗口,且依赖 ui 线程消息循环。它不模拟底层硬件事件,只是向当前焦点控件发送 wm_keydown/wm_keyup 消息。
- 确保调用前目标窗口已激活:
Process.MainWindowHandle+SetForegroundWindow()(需 P/Invoke) - 避免在 Timer 或后台线程中直接调用——必须封送到 UI 线程(
Control.Invoke()或Dispatcher.Invoke()) - 某些安全上下文(如 UAC 提权后)会隔离输入,此时
SendKeys被系统静默丢弃 - 测试时别用记事本以外的程序起步——浏览器、IDE 等常拦截或重定义快捷键
用 InputSimulator 实现可靠按键模拟
InputSimulator(来自 WindowsInput NuGet 包)绕过焦点限制,直接调用 SendInput() API,能向任意窗口发键,包括最小化或后台窗口。但它需要管理员权限才能对其他会话(如锁屏时)生效,普通用户权限下仅对同会话有效。
- 安装:
Install-Package WindowsInput - 基本用法:
new InputSimulator().Keyboard.KeyPress(VirtualKeyCode.RETURN) - 组合键注意顺序:先
KeyDown(CTRL),再KeyPress(C),最后KeyUp(CTRL),否则可能被忽略 - 中文输入法状态下,
KeyPress('a')可能上屏成「啊」——应先切换到英文模式,或用TextEntry模式配合SendText()
模拟按键时如何处理目标窗口句柄和焦点
靠 FindWindow 或 EnumWindows 找窗口,比“等几秒再 SendKeys”靠谱得多。但窗口标题易变、进程名不唯一,得加多重校验。
- 优先用
Process.GetProcessesByName("notepad")获取进程,再取MainWindowHandle - 若主窗口为 0(如某些 Electron 应用),尝试遍历子窗口:
FindWindowEx()+GetClassName()判断 - 激活窗口后,用
IsWindowVisible()和IsIconic()确认是否真可交互,而非仅“存在” - 不要依赖
Sleep(100)等待——改用WaitForInputIdle()或轮询GetForegroundWindow() == targetHwnd
SendInput() 在远程桌面或虚拟机里失效怎么办
本质是 Windows 的 Session 隔离机制导致的:RDP 连接后,新会话无权向原会话注入输入。这不是代码 bug,是系统设计。
- 本地物理机跑脚本 → 目标也是本地进程:正常可用
- 脚本跑在 RDP 会话 A → 想控制同一台机器的会话 B:不行,除非用
WTSConnectSession切换会话(需管理员+登录凭据) - 虚拟机内运行 → 控制宿主机:不可能,违反虚拟化隔离原则
- 替代方案:把自动化逻辑打包成目标进程内的插件(如 AutoHotkey EXE 注入)、或改用 UIAutomation(
System.Windows.Automation)操作控件属性
事情说清了就结束。真正难的从来不是按哪个键,而是让系统相信“这个键确实该这时候按下去”。











