emWin(ucGUI)在PC机上模拟的按键响应多次解决办法
Contents
起因
菜单程序在 emWin(ucGUI) 的 PC 模拟器上调试时出现怪现象:按一次 ENTER,菜单连跳两级——本该从一级进到二级,结果直接落在三级菜单。
需求
- 按一次按键只响应一次,一级一级进入菜单
- “响应 ENTER 键消息后切换菜单”的既有架构不动,只在按键判定上找原因
技术实现原理
根因:模拟器“按下、松开”各发一条消息
emWin(ucgui) 在PC端的模拟器,默认的按键机制是"按抬都发送Msg",当在按下键盘时,会收到一个key值-1,在按键没有离开时一直维持,当按键松开时还发送一个key值-0的标记。所以在你手速多快的情况下都会有一个 key,1和key,0两个操作。程序中没有对按键的状态做判定,所以在PC上不管是按下,还是按下后离开都会进行响应(两次响应)。
把这两条消息放进菜单场景推演一遍,“连跳两级”就不奇怪了:在一级菜单按下 ENTER,第一条(按下)消息触发 WIN_Enter 切到二级菜单;手指还没离开,第二条(松开)消息到达——此刻收消息的已经是二级菜单的窗口,它的回调同样响应 ENTER,于是再 WIN_Enter 一次切到三级。一次物理按键 = 两条等价消息 = 两次窗口切换,这就是 BUG 的完整因果链。
修复思路:用 PressedCnt 把“电平”变“边沿”
WM_KEY 消息携带的 WM_KEY_INFO 里,除了 Key 还有一个 PressedCnt(当前按下的键计数):按下那条消息里它是 1,松开那条里它是 0。只放行 PressedCnt == 0 的一条、忽略其余,一次物理按键就恰好响应一次——本质上是把“电平触发”改成“边沿触发”:不看按键“维持着”的状态,只认状态变化那一下。
选松开(==0)而不是按下(==1)还有个附带好处:操作系统键盘的自动重复会连发多条“按下”消息,只认松开天然免疫连发。
代码实现
假设有以下的应用场景,一级菜单->(ENTER键)->二级菜单>(ENTER键)->三级菜单,即,在一级菜单是可以用ENTER键进入二级菜单,同样在二级菜单可以用ENTER键进入三级菜单,实现的方式是:响应ENTER键的消息,然后切换菜单。
|
|
以上代码是通过提前构造少的菜单树结构,然后在菜单节点响应ENTER键和ESC键完成进入和退出,从代码上看不出有什么问题了,但是在PC实际仿真的过程成发现,在一级菜单按下ENTER键时进入二级菜单,紧接着进入三级菜单。

解决方案
所以需要对ENTER键进行判断响应,每次按下ENTER键就响应一次:
|
|
代码说明
((WM_KEY_INFO *)(pMsg->Data.p))->Key从消息里取出键值,WM_KEY 的附加数据是一个指向 WM_KEY_INFO 的指针;- 判断
PressedCnt == 0是核心:只处理“松开”那条消息,把同一物理按键的另一半过滤掉; - WIN_Enter(0) 是业务动作(进入下级窗口),与按键过滤无关,换成任何“只该执行一次”的动作都成立。
踩坑记录与注意事项
- 按一次响应两次 / 菜单连跳两级:先查输入层是不是“按、抬各报一条消息”、应用层有没有按 PressedCnt 过滤——这是本类 BUG 的通用根因,与菜单逻辑写得多正确无关;
- 别用延时消抖对付这个问题:延时对付的是触点抖动(毫秒级电平抖动),按抬双报是两条合法消息,延时治标还引入卡顿;
- 每一级窗口的回调都要过滤:本例连跳两级的第二跳,就是二级菜单的回调也响应了那条“松开”消息——过滤逻辑要在所有响应按键的窗口里生效,不能只改第一个;
- 只认按下还是只认松开要想清楚:只认 PressedCnt==1 在键盘自动重复下会连发;只认 ==0 的响应时机是松手瞬间,交互上略有延迟感,按业务取舍;
- PC 模拟器与真机行为可能不同:按键消息的行为取决于输入层实现,上了真机要重新验证按键手感,别假设两边一致。
Author 软件开发大郭
LastMod 2022-01-19