emWin 之 WM_SetCallback 创建回调函数
Contents
起因
用 emWin 的 MESSAGEBOX 时,默认的 OK 按键行为不一定满足需求——比如要在关闭前做确认、清理资源,或者像本文一样需要删除整个对话框,但又不可能去改 emWin 的库源码。emWin 提供的正式入口就是 WM_SetCallback():把窗口的消息处理函数换成自己的。
需求
- MESSAGEBOX 里点 OK 按键后,删除整个消息框对话框
- 除这个行为外,窗口其余行为(重绘、可移动等)保持默认
技术实现原理
emWin 是消息驱动的
emWin 窗口管理器(WM)的核心机制是回调 + 消息:每个窗口创建时都带一个回调函数,系统把按键、触摸、定时器、重绘请求统统打包成 WM_MESSAGE 结构投递给这个回调。控件(BUTTON)自己不做业务,它把"被点了"这件事包装成 WM_NOTIFY_PARENT 消息发给父窗口,消息里携带三样东西:
pMsg->MsgId:消息类型(这里是 WM_NOTIFY_PARENT);pMsg->hWinSrc:消息来源——哪个子控件发的,用WM_GetId()取出控件 Id;pMsg->Data.v:通知码 NCode(CLICKED / RELEASED 等)。
WM_SetCallback 与消息流向
WM_SetCallback(hWin, _cbDialogMessage) 把窗口原来的内部默认回调替换成我们的函数,之后这个窗口的所有消息都先进 _cbDialogMessage。整个链路是:
- BUTTON 被按下/松开 → BUTTON 内部回调给父窗口发 WM_NOTIFY_PARENT;
- 这条消息进入替换后的
_cbDialogMessage; - 只关心 GUI_ID_OK + WM_NOTIFICATION_RELEASED,命中就
WM_DeleteWindow(hWin)删除对话框; - 其他所有消息交给
WM_DefaultProc(pMsg)走默认处理。
关键在第 4 步:不认识的消息必须交还默认过程。窗口的重绘(WM_PAINT)、移动(MOVEABLE 特性)、子窗口管理都依赖默认处理。替换回调等于接管了这个窗口的全部消息,而不只是想处理的那一条——漏掉 default 分支,轻则窗口不刷新、拖不动,重则花屏。这就是自定义回调后最常见 BUG 的原理根因。
代码实现
下段代码就是通过 MESSAGEBOX 控件的 OK 按键实现删除信息框的对话框,即消息框的背景窗口 DIALOG 删除了,留下了 TEXT & BUTTON
|
|
代码说明
MESSAGEBOX_Create(..., GUI_MESSAGEBOX_CF_MOVEABLE):创建可移动的消息框,返回背景窗口(DIALOG)句柄;WM_SetCallback(hWin, _cbDialogMessage):接管这个背景窗口的消息处理;WM_NOTIFY_PARENT分支:用WM_GetId(pMsg->hWinSrc)取来源控件 Id,通知码在pMsg->Data.v里;- 只在
WM_NOTIFICATION_RELEASED(松开)时执行删除,CLICKED(按下)忽略——避免按下去的瞬间误触发; default: WM_DefaultProc(pMsg):其余消息交还系统,保持默认行为。
踩坑记录与注意事项
- 漏 default 分支:窗口不重绘、拖不动、花屏。替换回调是全接管,必须把不处理的消息还给
WM_DefaultProc; - CLICKED 和 RELEASED 别混:CLICKED 是按下瞬间、RELEASED 是松开,做"删除/确认"类动作用 RELEASED 更符合操作直觉;
- 回调里别做耗时操作:回调在 WM 消息循环的上下文里执行,长循环会卡住整个 GUI,多任务版本里还会导致喂狗超时;
- 多个窗口可共用一个回调:回调是普通 C 函数,区分"是谁的消息"要靠
pMsg->hWin,不要在回调里写死窗口句柄; - 删除要删对句柄:
WM_DeleteWindow会连同子窗口一起删;如果删完发现"留下了 TEXT & BUTTON"(本文观察到的现象),说明删到的只是背景窗口,需要确认传入的 hWin 是不是整个对话框。
Author 软件开发大郭
LastMod 2022-01-07