ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

屏幕软键盘实战:消息注入、焦点抢占与输入法兼容全攻略

屏幕软键盘实战:消息注入、焦点抢占与输入法兼容全攻略 简介输入模拟是Windows自动化与辅助工具的基础能力涉及键盘消息注入、焦点管理、输入法兼容等关键技术。理解底层原理才能应对不同程序的输入策略。在触屏一体机、展厅签到、工业手持终端等无键盘场景下软键盘作为输入替代方案需要解决“模拟按键如何被目标窗口正确识别”的核心问题。围绕屏幕软键盘的工程实现路径从窗口样式、键位映射到SendInput调用代码级讲解针对焦点抢占、UIPI限制、组合键时序等实测踩坑逐一攻破。适合有意开发虚拟键盘或需要后台模拟输入的程序员参考。1. 屏幕软键盘.zip一个置顶小窗口解决向其他程序输入字符的问题拿到“屏幕软键盘.zip”这类包解压后你会发现它往往不是网页也不是小程序工程而是一个独立的桌面小工具悬浮在屏幕最上层点击它的键帽它把鼠标点击翻译成标准键盘消息送进当前前台窗口或其他指定程序的输入框。这个需求常出现在触屏一体机、展台签到、仓库手持终端等场景——没有实体键盘又要操作另一个程序录入内容。这篇文章就围绕“自己动手实现一个屏幕软键盘”展开从消息通道选型、键位映射表、焦点策略到输入法兼容按一条可复现的 Win32 实现路径讲透适合做触摸屏配套工具、自助终端和演示机的人照着落地。2. 输入消息的三个通道SendMessage、SendInput 与低层钩子的适用边界屏幕软键盘的本质是把鼠标点击转译成键盘消息再注入目标程序。这里最大的坑在于同样一个“字符 A”可以通过完全不相同的路径送达而目标程序对不同路径的“信任度”完全不同。选错通道记事本能打字游戏里却没反应或者反过来。2.1 PostMessage 定向投递适合后台窗口但对游戏与安全输入失效PostMessage 和 SendMessage 直接把 WM_KEYDOWN、WM_CHAR、WM_KEYUP 消息塞进目标窗口的消息队列或窗口过程。它的最大优点是“后台输入”目标窗口不需要获得焦点软键盘可以一边保持自己的置顶窗口一边往另一个窗口发消息。很多截屏工具、按键精灵脚本就是靠这一招。但问题也很明显。这类消息没有经过系统输入线程GetAsyncKeyState 和 GetKeyboardState 这类状态查询接口完全读不到DirectInput、Raw Input 以及大量自绘控件的输入逻辑也不认它。也就是说PostMessage 适合的目标窗口是标准的 Win32 编辑框、RichEdit、部分 MFC 控件以及没有做键盘状态自检的普通程序。游戏、安全输入框、带加密键盘的网银控件基本都会无视。在我自己项目里的经验是PostMessage 更适合做“后台快捷键”而不是做通用输入。原因是它构造消息序列时很容易出错——只发 WM_CHAR 不发 WM_KEYDOWN很多控件会丢字符发了 WM_KEYDOWN 但 dwlParam 里的扫描码、重复计数不对某些控件也会拒绝处理。真要在后台往目标窗口打字至少要构造完整的 WM_KEYDOWN → WM_CHAR → WM_KEYUP 序列并且把 lParam 里的 repeat count、scan code、extended key 标志都填对这比按一下键码要累得多。2.2 SendInput 全局模拟最接近真实按键但要盯住焦点SendInput 把 KEYBDINPUT 结构注入系统输入队列系统会把它当作物理键盘产生的事件同等处理。这意味着GetAsyncKeyState 能读到状态DirectInput 能读到Raw Input 也能读到几乎所有按“真实键盘”逻辑运行的程序都能识别。因此当前前台窗口是谁字符就进谁软键盘自己绝不能抢焦点。这是绝大多数屏幕软键盘的正路也符合标题里“向其他程序中输入字符”的诉求。缺点也很直观目标窗口必须处于激活状态。如果用户在触摸屏上点了软键盘软键盘的窗口却抢走了焦点那字符就会打到软键盘自己的窗口里这在后面会专门讲怎么规避。SendInput 另一个隐蔽限制是 UIPI用户界面特权隔离。如果目标窗口以管理员权限运行而你的软键盘是普通权限那么 SendInput 会静默失败——字符进不去也不报错。这是很多人在排查时最容易忽略的一点明明代码没问题换一个窗口就能打字唯独目标程序没反应。2.3 低层键盘钩子适合拦截与改键不适合当注入主力WH_KEYBOARD_LL 低层键盘钩子经常被用来做全局热键、按键拦截、按键映射。它也能改键钩子回调里把按键消息吞掉再向目标窗口注入另一个键。但把这个机制当作屏幕软键盘的主输入通道属于吃力不讨好。原因有三。第一低层钩子回调运行在安装钩子的线程上下文中任何阻塞都会拖慢整个系统的键盘响应第二如果钩子回调里调用 SendInput 模拟按键极易形成递归——SendInput 产出的按键事件同样会经过低层钩子你不加递归标志就会把自己模拟的按键再拦一遍第三钩子回调里做窗口查找、命中测试这类耗时操作会让系统键盘出现肉眼可见的卡顿。所以我的结论很明确低层钩子在软键盘项目里只适合做“开关”或“屏蔽物理按键”不适合做输入主力。要是看到有人推荐用低层钩子实现软键盘输入多半只是炫技不是最优方案。2.4 三个通道的选型对照表通道是否更新 GetAsyncKeyStateDirectInput/游戏是否识别是否需要目标窗口焦点典型适用场景PostMessage / SendMessage否否否后台操作标准 Win32 控件SendInput是是是屏幕软键盘首选触摸屏录入低层键盘钩子注入是是是改键工具、按键拦截不建议注入选型结论你的屏幕软键盘一定以 SendInput 为主。只有当明确遇到“目标程序是标准控件且不希望它抢焦点”这种特殊约束时才考虑加一条 PostMessage 的定向投递作为辅助这就是后面第 4 章要展开的“混合模式”。3. 搭建第一个可用屏幕软键盘窗口样式、键位映射与点击输入通道选好之后就直接进入实现。我以 Win32 API 为例不引入 MFC、Qt 或 Electron原因是软键盘本身就是一个“小、快、不抢焦点”的组件用原生 API 做出来的成品只有几十 KB依赖少部署时打个 zip 解压就能跑这正好对应你在搜索引擎里见到“屏幕软键盘.zip”这种包的感觉。3.1 窗口样式与创建参数置顶、不抢焦点、可调透明度软键盘窗口不能是普通主窗口否则它会出现在任务栏、点击时会抢焦点、背景不透明还会挡住后面内容。创建一个适合软键盘的窗口扩展样式是关键#include windows.h const wchar_t* kClassName LScreenSoftKey; const int kWindowW 640; const int kWindowH 220; const int kAlpha 220; // 0 ~ 255220 表示轻度半透明 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); HWND CreateSoftKeyWindow(HINSTANCE inst) { WNDCLASSEXW wc { sizeof(wc) }; wc.lpfnWndProc WndProc; wc.hInstance inst; wc.hCursor LoadCursorW(NULL, IDC_HAND); wc.lpszClassName kClassName; RegisterClassExW(wc); HWND hwnd CreateWindowExW( WS_EX_NOACTIVATE | // 点击时不激活本窗口焦点不被抢走 WS_EX_TOOLWINDOW | // 不在任务栏显示图标 WS_EX_TOPMOST | // 置顶显示 WS_EX_LAYERED, // 允许设置透明度 kClassName, LScreenSoftKey, WS_POPUP, 0, 0, kWindowW, kWindowH, NULL, NULL, inst, NULL); SetLayeredWindowAttributes(hwnd, 0, kAlpha, LWA_ALPHA); return hwnd; }四个扩展样式缺一不可。WS_EX_NOACTIVATE 保证用户点击软键盘时当前前台窗口不会失焦WS_EX_TOOLWINDOW 让软键盘不出现在 AltTab 切换列表和任务栏WS_EX_TOPMOST 保证软键盘永远覆盖在目标程序上方WS_EX_LAYERED 配合 SetLayeredWindowAttributes 做半透明透明度值一般取 200~240太透明了键帽看不清太实了又挡内容。注意WS_EX_NOACTIVATE 只是“不激活”但鼠标点击消息仍可能被某些窗口管理器逻辑处理掉所以还要在窗口过程里显式拦截 WM_MOUSEACTIVATE 返回 MA_NOACTIVATE双重保险。3.2 按键映射表用坐标网格描述键位软键盘的键位不能写死在绘制代码里否则改一个键帽位置就要重编译。常见的做法是定义一张键位结构体数组每个键包含位置、尺寸、显示文本、虚拟键码和修饰键标志struct KeyDef { int x, y, w, h; // 在软键盘窗口内的像素位置 const wchar_t* label; // 键帽上显示的文字 WORD vk; // 虚拟键码如 A、VK_SHIFT WORD scan; // 扫描码0 表示交给系统从 vk 映射 BYTE modMask; // 组合键标志1Shift2Ctrl4Alt }; static KeyDef g_keys[] { {0, 0, 70, 50, LEsc, VK_ESCAPE, 0, 0}, {75, 0, 70, 50, LTab, VK_TAB, 0, 0}, {150, 0, 70, 50, LCaps, VK_CAPITAL,0, 0}, {225, 0, 70, 50, LShift, VK_SHIFT, 0, 1}, {0, 55, 90, 50, LCtrl, VK_CONTROL,0, 2}, {100, 55, 60, 50, LA, A, 0, 0}, // ... 字母、数字、方向键、空格键按同样格式继续铺 {540, 170, 100, 50, LSpace, VK_SPACE, 0, 0}, };这里有几个注间点为什么不写死。label 与 vk 是两回事Caps Lock 键帽上写着“Caps”但虚拟键码是 VK_CAPITALA 键帽上写“A”虚拟键码就是 0x41。scan 字段一般填 0SendInput 时会用 MapVirtualKey 自动生成扫描码但如果你服务的设备键盘布局特殊比如日语键盘就得在表里硬编码扫描码否则会出现“按软键盘 A 键目标程序收到的是别的字符”这种奇怪问题。modMask 是处理 Shift字母这类组合键的关键。单独点 A 键modMask 为 0要做一个“ShiftA”的键帽就把 modMask 置 1点击时先按下 Shift再按 A然后按相反顺序松开。这个顺序放后面代码里说明。3.3 命中测试与 SendInput 调用点击键帽发出键盘事件窗口过程收到 WM_LBUTTONDOWN 时根据鼠标坐标遍历上面的 g_keys 数组用 PtInRect 做命中测试命中后就调用 SendInput 发送键盘事件void SendKey(WORD vk, BOOL down) { INPUT in { 0 }; in.type INPUT_KEYBOARD; in.ki.wVk vk; in.ki.wScan MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); in.ki.dwFlags down ? 0 : KEYEVENTF_KEYUP; in.ki.time 0; in.ki.dwExtraInfo 0; SendInput(1, in, sizeof(INPUT)); } void ClickKey(const KeyDef* key) { if (key-modMask 1) SendKey(VK_SHIFT, TRUE); if (key-modMask 2) SendKey(VK_CONTROL, TRUE); if (key-modMask 4) SendKey(VK_MENU, TRUE); SendKey(key-vk, TRUE); Sleep(2); // 极小延迟防止目标程序把按下抬起识别为抖动 SendKey(key-vk, FALSE); if (key-modMask 1) SendKey(VK_SHIFT, FALSE); if (key-modMask 2) SendKey(VK_CONTROL, FALSE); if (key-modMask 4) SendKey(VK_MENU, FALSE); } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_LBUTTONDOWN: { int px GET_X_LPARAM(lp); int py GET_Y_LPARAM(lp); for (size_t i 0; i _countof(g_keys); i) { RECT rc { g_keys[i].x, g_keys[i].y, g_keys[i].x g_keys[i].w, g_keys[i].y g_keys[i].h }; POINT pt { px, py }; if (PtInRect(rc, pt)) { ClickKey(g_keys[i]); break; } } return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wp, lp); }这段代码里Sleep(2) 是刻意加的。注入的按键不像物理键盘有硬件层面的按下时长如果按下和抬起间隔太短有些程序会当成无效抖动丢弃2 毫秒只是底线如果目标程序对时序敏感比如某些游戏需要 10~20 毫秒才能识别就应该把 Sleep 值提到配置里。组合键的按下顺序是“修饰键先按主键后按抬起时镜像”。这套逻辑对 Shift字母、CtrlC、AltTab 都成立。要注意的是VK_MENU 对应键盘上的 Alt 键如果只按 Alt 不按其他键很多程序会弹出菜单栏焦点所以 Alt 组合键要格外确认按键序列完整。3.4 长按连续输出与组合键把“按住”效果补回来真实键盘按住方向键不放光标会连续移动但 SendInput 每调用一次只产生一次按键事件不会自动重复。要让软键盘支持“长按”必须自己实现 autorepeat#define TIMER_REPEAT 1001 #define REPEAT_DELAY_MS 45 // 重复间隔真实键盘系统默认约 250ms 后开始 static const KeyDef* g_pressedKey nullptr; static HWND g_hwndOwner nullptr; case WM_LBUTTONDOWN: { // 找到命中的 key 后 g_pressedKey key; g_hwndOwner hwnd; SendKey(key-vk, TRUE); SetTimer(hwnd, TIMER_REPEAT, REPEAT_DELAY_MS, NULL); return 0; } case WM_TIMER: if (wp TIMER_REPEAT g_pressedKey) { // 只发按下不发抬起Windows 会自动把它当作连续按键处理 SendKey(g_pressedKey-vk, TRUE); } return 0; case WM_LBUTTONUP: if (g_pressedKey) { KillTimer(hwnd, TIMER_REPEAT); SendKey(g_pressedKey-vk, FALSE); g_pressedKey nullptr; } return 0;这里有个细节值得强调WM_LBUTTONUP 里必须发一次 keyup否则如果用户只点击了一下目标程序会认为这个键一直被按住。自动重复的间隔建议 45~60 毫秒比系统默认的重复延迟快得多这是为了照顾触摸屏上“点到为止”的操作习惯如果做的是模拟实体键盘的工具可以改成 250 毫秒起步的经典节奏。4. 把键盘接进目标程序焦点策略、输入法兼容与三种对接方式窗口能弹出来了按键也能发出去了但字符能不能落到目标程序里是另一回事。这一章处理的都是实际使用中让软键盘“不好用”的细节焦点被抢、输入法弹拼音、后台窗口收不到。4.1 焦点策略WS_EX_NOACTIVATE 与 WM_MOUSEACTIVATE 缺一不可只设置 WS_EX_NOACTIVATE 还不够。窗口样式只是告诉系统“不要因为点击而激活”但鼠标点击消息本身仍可能触发默认激活逻辑。正确的做法是在窗口过程里显式拦截case WM_MOUSEACTIVATE: return MA_NOACTIVATE;MA_NOACTIVATE 告诉系统这次鼠标点击不要激活这个窗口也不要取消当前前台窗口的激活状态。两个机制配合后软键盘点击键帽时焦点始终留在目标程序里SendInput 才能准确地把字符送进目标窗口。这条经验是从一个实际项目里踩出来的早期版本只设置了 WS_EX_NOACTIVATE发现点软键盘上的“A”字符偶尔会跑到软键盘自己的窗口里或者干脆丢进任务栏。后来加上 WM_MOUSEACTIVATE 返回 MA_NOACTIVATE问题立刻消失。这个现象在做触摸屏程序时尤其明显因为触摸点击和鼠标点击的激活行为不完全一致。4.2 输入法兼容KEYEVENTF_UNICODE 怎么发中文与特殊符号屏幕软键盘如果只面向 ASCII 字符直接用虚拟键码就够了。但要往目标程序输入中文、日文或特殊符号问题就复杂了。常见做法有两种。第一种是切换目标输入法到英文模式然后发字母第二种是剪贴板粘贴。前者会干扰用户的输入法状态后者会被很多安全控件拦截。我一般用 KEYEVENTF_UNICODE 标志它告诉系统“直接把这个 Unicode 字符交给当前窗口”绕开键盘布局和输入法状态void SendUnicode(wchar_t ch) { INPUT in { 0 }; in.type INPUT_KEYBOARD; in.ki.wScan ch; in.ki.dwFlags KEYEVENTF_UNICODE; SendInput(1, in, sizeof(INPUT)); in.ki.dwFlags KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; SendInput(1, in, sizeof(INPUT)); }KEYEVENTF_UNICODE 模式下 wVk 必须为 0字符码放在 wScan 字段。这段代码能把“中”“日”“€”这类字符直接送进标准输入框而且不依赖目标程序的键盘布局。但要注意边界KEYEVENTF_UNICODE 对老式 Win32 控件、某些游戏自绘输入框支持不好它们只认 WM_CHAR 消息携带的字符不认这个注入通道。所以实际项目里我会把“Unicode 模式”做成开关默认是虚拟键码模式只有目标程序确认支持 Unicode 注入了才开。4.3 三种对接模式前台自动、后台定向、混合切换不同场景下“向其他程序输入字符”的诉求不一样我一般把对接方式做成可配置的模式目标窗口获取方式按键投递方式适用场景前台自动实时调用 GetForegroundWindowSendInput直接落进当前前台窗口触摸屏一体机、自助终端焦点始终在目标程序后台定向FindWindow / 用户拖拽取窗口句柄SendMessage / PostMessage 构造完整消息序列目标窗口不能抢焦点或需要同时操作多个窗口混合模式两种方式并存前台窗口变化时自动切换投递路径复杂演示系统多个程序间来回切换前台自动是主力也是第 3 章代码默认的行为。后台定向需要多一步提供一个“选择目标窗口”按钮点击后鼠标变成十字准星用户在目标窗口上点击系统用 WindowFromPoint 取到窗口句柄并保存。之后点击软键盘键帽时不再用 SendInput而是把 WM_KEYDOWN、WM_CHAR、WM_KEYUP 序列 PostMessage 给保存的那个窗口句柄。这里有个容易翻车的点PostMessage 构造的消息序列必须包含足够的 lParam 信息。常见的错误是只发 WM_CHAR 带一个字符结果标准编辑框能显示但游戏或自绘控件直接忽略。正确序列里WM_KEYDOWN 的 lParam 要带上重复计数 1、扫描码、是否扩展键等信息WM_CHAR 的 wParam 放字符WM_KEYUP 也要按规范填。所以后台定向模式只建议在“目标窗口是标准输入控件”时使用不要奢望它通吃所有程序。4.4 全局参数清单把键位尺寸、延迟和模式做成配置文件软键盘做到能跑以后下一步就是参数化。项目里我个人会留出一份全局配置避免每次调参都改代码重编译参数默认值建议范围作用RepeatDelayMs4515~120长按自动重复间隔游戏要求快则调小KeyPressDurationMs20~50单次点击按下与抬起间隔Alpha220160~255窗口透明度StickyMods10~1Shift/Ctrl/Alt 是否支持粘滞点一下保持再点字母ImeSafeMode10~11 时用 KEYEVENTF_UNICODE0 时用标准虚拟键码LayoutFilelayout.ini文件名键位布局文件路径TargetMode00前台,1后台,2混合对接目标程序的方式StickyMods 这个参数值得单独解释一下。软键盘上没有物理键盘那种“按住 Shift 再按字母”的动作所以要么点击字母键时自动补 Shift要么让 Shift 键变成“点一下保持、再点一下取消”的粘滞模式。我一般默认开粘滞因为它在触摸屏上最顺手而且不需要为每个组合键单独设计键帽。5. 屏幕软键盘常见问题排查6 条让字符进不了窗的踩坑记录这一章是实战记录。以下每一条都是我在做软键盘过程中真实遇到过、并且花过不少时间才找到原因的坑按“现象 → 原因 → 解决”的格式写方便你直接对照排查。5.1 管理员权限窗口收不到输入UIPI 把消息挡在门外现象软键盘对普通记事本打字正常切到某台机器上向一个以管理员身份运行的程序输入时字符完全进不去SendInput 不报错目标程序也没有任何反应。期间试过换窗口、换延迟、甚至换电脑问题依旧。原因Windows 的用户界面特权隔离UIPI会阻止较低完整性级别的进程向较高完整性级别窗口注入输入或发送消息。你的软键盘是普通权限目标程序是管理员权限SendInput 的键盘事件在系统层就被拦掉了。解决给软键盘自身添加 requireAdministrator 的清单manifest让它也以管理员权限运行。这是最直接的方案。另外一个备选是让用户手动以管理员身份启动软键盘但体验很差。这条排查经验后来成了我的固定动作遇到“某个程序打不进字”第一件事不是看代码而是看目标进程的权限级别。5.2 游戏或自绘控件无视 WM_CHAR要模拟设备而非投递消息现象软键盘用 PostMessage 向一个游戏内置聊天框发送 WM_CHAR字符不上屏改成 SendMessage 也一样但切到记事本就能正常输入。原因这类程序不走标准 Windows 消息循环而是用 DirectInput 或 Raw Input 直接轮询键盘设备状态。PostMessage 构造的消息根本没有进入系统硬件输入队列程序自然读不到。解决切换到 SendInput 通道让注入的按键事件被系统当作真实设备输入处理。如果还是不行检查按下与抬起之间的延迟是否太短某些游戏会把 1 毫秒内的按下抬起当成信号抖动需要把 KeyPressDurationMs 调到 10~20 毫秒。这个坑的教训是游戏类目标程序永远优先选 SendInput不要为了“后台输入”而坚持 PostMessage。5.3 点击软键盘后目标窗口失焦NOACTIVATE 没写全现象软键盘和正常程序同时开着点软键盘的键帽字符没有进目标输入框反而把软键盘自己变成了活动窗口。原因窗口样式设置了 WS_EX_NOACTIVATE但窗口过程没有处理 WM_MOUSEACTIVATE或者两者都做了但某个键帽点击时调用了 SetFocus 之类会改变焦点的 API。解决窗口过程里显式返回 MA_NOACTIVATE同时检查代码里有没有隐藏的 SetFocus 调用。触摸屏环境下尤其要注意触摸点击有时会走不同的激活路径建议用真实触摸设备测试而不是只拿鼠标点两下就收工。5.4 长按方向键只走一步autorepeat 需要自己实现现象按住软键盘上的右方向键目标输入框里光标只移动一格就停了必须松手再按才能继续移动。原因SendInput 每次调用只产生一次按键事件不会像物理键盘那样触发系统自动重复。系统重复机制只服务于真实硬件中断对注入事件不生效。解决用 SetTimer 实现自动重复。按下方向键时发第一次 keydown随后每 45 毫秒再发一次 keydown不发 keyup直到鼠标松开才发 keyup。重复间隔可在配置里调整想慢就调大到 80~120 毫秒。5.5 组合键顺序不对Shift字母变成别的符号现象软键盘上做了一个“ShiftA”的键帽点击后目标程序打出的是“*”或其他符号而不是大写 A。原因组合键的按下与抬起顺序出错。先松开了 Shift 再松开 A或者按下顺序变成了“先 A 后 Shift”字符结果就会随着键盘布局和当前状态产生变化。解决严格按照“修饰键先按下 → 主键按下 → 主键抬起 → 修饰键抬起”的顺序。代码里先发 Shift 的 keydown再发 A 的 keydown抬起时必须先发 A 的 keyup再发 Shift 的 keyup。顺序错了轻则字符不对重则触发系统快捷键。5.6 中文输入法开着字母上屏变成拼音现象目标程序的输入法处于中文模式点软键盘上的字母键上屏的不是字母而是拼音或汉字候选框。原因标准虚拟键码注入不会切换输入法状态目标窗口当前是什么输入法注入的字母就会走什么输入法。解决两种做法。一是点击软键盘前把目标线程的输入法状态切到英文模式调用 ImmSetConversionStatus 关闭输入法二是启用字符直通用 KEYEVENTF_UNICODE 发送字符绕开输入法。如果目标程序是密码框或扫码录入这类要求绝对准确的场景建议直接用 KEYEVENTF_UNICODE。6. 让它像真键盘一样耐操布局外置、热键开关与消息可视化验证平面功能做齐以后再往前一步就是工程化收尾。6.1 布局外置让键盘布局从配置文件加载键位映射表写死在代码里会导致“加一个键帽、改一个位置都要重编译”。我通常把布局文件做成纯文本每行描述一个键帽# layout.ini: x,y,w,h,label,vk,scan,modifier 0,0,70,50,Esc,27,0,0 75,0,70,50,Tab,9,0,0 150,0,70,50,Caps,20,0,0 225,0,70,50,Shift,16,0,1 0,55,90,50,Ctrl,17,0,2 100,55,60,50,A,65,0,0 540,170,100,50,Space,32,0,0程序启动时逐行解析把结果填进 g_keys 数组。vk 列直接用十进制虚拟键码modifier 列沿用 1Shift、2Ctrl、4Alt 的位标志。这样运维人员调整键帽位置和大小只需要改文本文件不需要碰代码。6.2 全局热键开关不需要时一键隐藏软键盘不能一直霸占屏幕。我习惯注册一个全局热键用来快速显示或隐藏软键盘RegisterHotKey(hwnd, 0x01, MOD_ALT, K); case WM_HOTKEY: if (wp 0x01) { BOOL visible IsWindowVisible(hwnd); ShowWindow(hwnd, visible ? SW_HIDE : SW_SHOW); } break;热键建议选“不常用且组合键不易误触”的搭配比如 AltK。同时要处理注册失败的情况如果另一个程序已经占用了这个热键RegisterHotKey 返回 FALSE这时应该把错误打印出来并换一个热键而不是静默忽略。6.3 验证消息序列用 Spy 检查目标窗口收到的键盘消息写完软键盘怎么确认它真的做到了“向其他程序输入字符”我的固定做法是用 Spy 附加到目标窗口观察按下键帽后窗口过程收到的消息序列。一个健康的序列应该是WM_KEYDOWNwParam 为虚拟键码→ WM_CHARwParam 为字符→ WM_KEYUPwParam 为虚拟键码。如果只看到 WM_KEYDOWN 没有 WM_CHAR说明目标控件不认为这是一个完整按键如果 WM_KEYDOWN 的 wParam 与键帽显示文字不符检查扫描码映射和键盘布局如果消息序列完全看不到说明按键没进目标窗口优先查焦点和 UIPI 权限。还有个更直观的验证办法自己写一个极简的调试目标程序里面只放一个编辑框和一个 WM_KEYDOWN 日志软键盘点一个键日志记录一条。这样能一眼看出是“消息没到”还是“到了但被目标程序忽略”。做这类工具做得多了我个人最大的教训是别在功能没跑通前急着调样式。先把最简窗口建出来确认 SendInput 能把 A 打进去再慢慢加透明度、粘滞键、布局文件。这样每一步出问题都知道是刚改的那一处。希望帮到你。本文还有配套的精品资源点击获取
返回列表