起因

带键盘的嵌入式设备(工控面板、遥控器操作的界面)没有鼠标和触摸,或者触摸只是辅助——用户在对话框里填完一个编辑框,要靠按键跳到下一个控件。这就要求程序能控制“焦点”:焦点在哪个控件上,按键就作用在哪个控件上。emWin 对此有一套现成机制,这篇把机制和用法整理清楚。

需求

  • 在对话框中把焦点设到指定控件(比如开机后默认聚焦第一个输入框)
  • 用按键在控件间前后切换焦点(TAB 下一个 / BACKTAB 上一个)
  • 判断当前焦点落在哪个控件上,做条件处理

技术实现原理

焦点的本质:键盘消息的路由

emWin 的窗口管理器给每个窗口维护一个“是否拥有输入焦点”的状态,同一时刻一个对话框里只有一个控件持有焦点。键盘消息的目标窗口就是焦点窗口:GUI_SendKeyMsg() 把键值发往当前焦点窗口;如果此刻没有窗口持有焦点,它就退化为 GUI_StoreKeyMsg() 把数据存进键盘缓冲器,等有窗口取键时再消费——这就是下文手册摘录里那两段描述的原理含义。

WM_SetFocus(hItem) 直接把焦点交给指定窗口,WM_HasFocus(hItem) 查询焦点是否在某窗口上,WM_GetDialogItem(hDlg, Id) 用控件 ID 换句柄——三个函数合起来就是“指定焦点、查询焦点”的全部入口。

TAB 切换的原理:窗口管理器代劳遍历

对话框里的控件在窗口管理器里按创建顺序连成链。把 GUI_KEY_TAB 作为一条按键消息发出去,窗口管理器会自动把焦点移到下一个可聚焦控件,GUI_KEY_BACKTAB 反向移动——相当于系统替你做了链表遍历,应用层不用关心控件顺序。注意 Pressed 参数:1 表示按下、0 表示松开,发 1 即可,切换消费的是键值本身。

API 参考(手册摘录)

驱动层API

键盘驱动层处理键盘消息函数。这些程序会在具体键(或组合键)被按下或松开时通知窗口管理器。
下表按字母顺序列出了驱动层键盘程序。详细描述如下。

|GUI_StoreKeyMsg()|把消息存储于指定键。|
|GUI_SendKeyMsg()|把消息发送至指定键。|

GUI_SendKeyMsg()

描述

把键盘数据发送到输入焦点所在窗口。如果没有窗口有输入焦点,则调用GUI_StoreKeyMsg()函
数将数据存储至输入缓冲器之中。

原型

1
voidGUI_SendKeyMsg(int Key, int Pressed);

|参数 |描述 |
|Key |可以是任意扩展 ASCII 字符 (范围为 0x20 至 0xFF)或者任意预定义的 emWin 字符编码。|
|Pressed|1 按下状态。0 松开 (未按下)状态。|

GUI_StoreKeyMsg()

描述

把消息数据 (Key, PressedCnt) 存进键盘缓冲器。

原型

1
void GUI_StoreKeyMsg(int Key, int Pressed);

应用层 API

下表按字母顺序列出了应用层键盘程序。详细描述如下。

程序 描述
GUI_ClearKeyBuffer() 清除键缓冲器。
GUI_GetKey() 返回键缓冲器中的内容。
GUI_StoreKey() 把键存储于缓冲器中。
GUI_WaitKey() 等待键被按下

实现步骤

ucGui(emWin)的对话框中怎么切换控件的焦点?

  1. 获得控件的句柄hItem=WM_GetDialogItem(HWin,EditLocateGet());
  2. 聚焦:WM_SetFocus(hItem);
  3. 切换控件的焦点,向窗口管理器发 TAB / BACKTAB 键消息即可:
1
2
GUI_SendKeyMsg(GUI_KEY_TAB, 1);//选择下一个聚焦控件
GUI_SendKeyMsg(GUI_KEY_BACKTAB, 1);//选择上一个聚焦控件

例如:

1
2
3
if(WM_HasFocus(WM_GetDialogItem(HWin,ID_BUTTON_MINUS))){ //如果聚焦在"-"按键
    GUI_SendKeyMsg(GUI_KEY_TAB, 1);//选择下一个聚焦控件
}

代码说明

  • WM_GetDialogItem(HWin, ID):用控件 ID 换句柄,句柄是后续一切焦点操作的钥匙;
  • WM_SetFocus(hItem):把输入焦点交给该控件,之后的键盘消息默认路由到它;
  • GUI_SendKeyMsg(GUI_KEY_TAB, 1):向焦点所在窗口发 TAB 键,窗口管理器把焦点移到下一个可聚焦控件;GUI_KEY_BACKTAB 反向;
  • 例如代码:WM_HasFocus 判断当前焦点是否在“-”按键上,是则发 TAB 跳走——条件切换的典型写法。

踩坑记录与注意事项

  • 发了 TAB 界面没反应:GUI_SendKeyMsg 的目标是“当前焦点窗口”,此刻若没有窗口持有焦点,消息会进键盘缓冲而不是触发切换——先 WM_SetFocus 给对话框里任一控件垫底;
  • 切换顺序 = 创建顺序:TAB 的下一个/上一个按控件的窗口链顺序走,界面上的视觉排列不影响它;顺序不对要调控件创建顺序,不是挪坐标;
  • 纯显示控件不在焦点链里:TEXT 这类只读控件不参与 TAB 遍历,布局里夹着它们不会“空跳”;
  • 别在焦点消息处理里再发切换键:焦点变化会触发 WM_SET_FOCUS / WM_KILL_FOCUS,在这些消息的处理里再发 TAB 容易一次按键连环切换;
  • 触摸与键盘焦点是两套输入:纯键盘导航的界面要自己用 WM_SetFocus 摆好初始焦点,别指望用户碰一下屏幕帮你把焦点摆对。