当前位置: 首页 > news >正文

emwin多页面切换:零基础实现界面跳转逻辑

从零开始玩转 emWin:手把手教你实现多页面平滑跳转

你有没有遇到过这样的场景?
刚把 LCD 屏点亮,画了个按钮、显示个温度值,心里正美滋滋,老板突然说:“这个界面太单调了,加个设置菜单,点进去还能返回来。”
然后你就懵了——怎么跳?跳完怎么回来?要不要清屏?会不会闪?内存够不够?

别慌。今天我们就用emWin,从零开始,不讲虚的,一步步带你实现一个真正可用的多页面跳转系统。不需要懂太多底层原理,也不需要啃完几百页手册,只需要你会写 C 语言、能跑通 STM32 工程,就能照着做出来。


先搞清楚一件事:为什么不能“手动清屏重绘”?

很多初学者在做 GUI 时,第一反应是:

“我当前显示主界面 → 用户一按按钮 → 我GUI_Clear()→ 然后重新画一个新的界面。”

听起来没问题?其实坑很多:

  • 清屏会闪烁(尤其是没有双缓冲的情况下);
  • 控件状态全丢了(比如滑动条位置、输入框内容);
  • 返回时得再画一遍原界面,效率低;
  • 代码耦合严重,改一个页面影响全局。

而 emWin 早就为你准备好了更聪明的办法:窗口管理机制(Window Manager, WM)

它就像手机上的 App 切换——微信和微博都在后台运行,你只是切到前台显示而已,并不需要每次点击都重启一次应用。


emWin 的“页面”到底是什么?

在 emWin 里,没有“页面”这个概念,只有“窗口(Window)”。

每个窗口是一个独立的绘制区域,有自己的坐标系、事件处理函数和生命周期。我们所谓的“页面”,其实就是一个全屏大小的顶层窗口,通常用FRAMEWIN来封装。

关键角色介绍

组件作用
WM_HWIN窗口句柄,相当于窗口的“身份证号”
FRAMEWIN_CreateEx()创建一个可作为页面容器的框架窗口
WM_CALLBACK_FUNC回调函数,负责处理消息(如按钮被点了)
WM_ShowWindow()/WM_HideWindow()显示/隐藏某个窗口
WM_BringToTop()把某个窗口提到最前面

这些 API 加起来,就是你实现页面跳转的全套工具箱。


实战:两个页面互相跳转

我们来做个最简单的例子:
有两个页面——主菜单页设置页,主菜单有个“进入设置”按钮,设置页有个“返回”按钮,点击即可来回切换。

第一步:定义页面创建函数(接口先行)

先建两个头文件,声明每个页面的创建函数:

// page_main.h #ifndef PAGE_MAIN_H #define PAGE_MAIN_H WM_HWIN CreateMainWindow(void); #endif
// page_settings.h #ifndef PAGE_SETTINGS_H #define PAGE_SETTINGS_H WM_HWIN CreateSettingsWindow(void); #endif

这样做的好处是模块清晰,后期想加“关于页”、“网络配置页”也方便扩展。


第二步:主页面实现(带跳转逻辑)

// page_main.c #include "DIALOG.h" #include "page_main.h" #include "page_settings.h" static WM_HWIN hMainWnd; // 保存主页面句柄 // 主页回调函数 static void _cbMain(WM_MESSAGE *pMsg) { WM_HWIN hItem; int Id; switch (pMsg->MsgId) { case WM_CREATE: // 标题文本 TEXT_CreateEx(100, 10, 120, 20, pMsg->hWin, WM_CF_SHOW, 0, 0, "主菜单"); // 跳转按钮 BUTTON_CreateEx(100, 50, 100, 40, pMsg->hWin, 0, 0, 0, "进入设置"); break; case WM_NOTIFY_PARENT: // 子控件发来的通知 Id = WM_GetId(pMsg->hWinSrc); // 获取哪个控件触发 if (pMsg->Data.v == WM_NOTIFY_CHILD_CLICKED) { if (Id == GUI_ID_BUTTON0) { // 假设这是第一个按钮 WM_HideWindow(hMainWnd); // 隐藏自己 CreateSettingsWindow(); // 显示设置页 } } break; default: WM_DefaultProc(pMsg); // 其他消息交给默认处理器 } } WM_HWIN CreateMainWindow(void) { // 创建全屏 FRAMEWIN 作为主页面 hMainWnd = FRAMEWIN_CreateEx(0, 0, 320, 240, WM_CF_SHOW, 0, 0, 0, "Main", _cbMain); FRAMEWIN_SetClientColor(hMainWnd, GUI_WHITE); // 设置背景色 return hMainWnd; }

注意几个细节:
- 使用FRAMEWIN_CreateEx创建窗口,并指定回调函数_cbMain
- 按钮通过BUTTON_CreateEx添加到主窗口中;
- 点击事件在WM_NOTIFY_PARENT中捕获;
- 当前页面隐藏后才创建新页面,避免叠加错乱。


第三步:设置页实现(支持返回)

// page_settings.c #include "DIALOG.h" #include "page_settings.h" #include "page_main.h" static WM_HWIN hSettingsWnd; static void _cbSettings(WM_MESSAGE *pMsg) { int Id; switch (pMsg->MsgId) { case WM_CREATE: TEXT_CreateEx(100, 10, 120, 20, pMsg->hWin, WM_CF_SHOW, 0, 0, "设置页"); BUTTON_CreateEx(100, 50, 100, 40, pMsg->hWin, 0, 0, 0, "返回"); break; case WM_NOTIFY_PARENT: Id = WM_GetId(pMsg->hWinSrc); if (pMsg->Data.v == WM_NOTIFY_CHILD_CLICKED) { if (Id == GUI_ID_BUTTON0) { WM_HideWindow(hSettingsWnd); // 隐藏自己 CreateMainWindow(); // 重建主页面(或复用) } } break; default: WM_DefaultProc(pMsg); } } WM_HWIN CreateSettingsWindow(void) { hSettingsWnd = FRAMEWIN_CreateEx(0, 0, 320, 240, WM_CF_SHOW, 0, 0, 0, "Settings", _cbSettings); FRAMEWIN_SetClientColor(hSettingsWnd, GUI_LIGHTGRAY); return hSettingsWnd; }

这里有个关键点:
当我们从设置页返回时,再次调用了CreateMainWindow()。但如果主页面之前只是被隐藏(没销毁),其实可以直接用句柄恢复显示,而不是重新创建。

那怎么优化?往下看。


进阶技巧:让页面“活着”,提升响应速度

频繁创建/销毁页面不仅慢,还容易造成内存碎片。更好的做法是:

常驻核心页面 + 按需加载次要页面

我们可以加一个简单的“页面管理器”:

// page_manager.h #ifndef PAGE_MANAGER_H #define PAGE_MANAGER_H void Page_SwitchToMain(void); void Page_SwitchToSettings(void); #endif
// page_manager.c #include "page_main.h" #include "page_settings.h" static WM_HWIN hMain = 0; static WM_HWIN hSetting = 0; void Page_SwitchToMain(void) { if (!hMain) { hMain = CreateMainWindow(); } else { WM_ShowWindow(hMain); WM_BringToTop(hMain); } if (hSetting) { WM_HideWindow(hSetting); } } void Page_SwitchToSettings(void) { if (!hSetting) { hSetting = CreateSettingsWindow(); } else { WM_ShowWindow(hSetting); WM_BringToTop(hSetting); } if (hMain) { WM_HideWindow(hMain); } }

现在你的跳转逻辑变成了:

// 在主页面按钮点击处: Page_SwitchToSettings(); // 在设置页返回按钮处: Page_SwitchToMain();

优点非常明显:
- 主页只创建一次,返回极快;
- 内存使用可控;
- 结构清晰,易于维护。


如何防止“越点越卡”?内存与资源管理建议

emWin 默认使用动态内存分配(GUI_ALLOC)。如果你发现跳几次就卡住甚至死机,大概率是内存不足或泄漏了。

几条铁律请牢记:

  1. 检查返回值
    所有Create函数都可能返回0,说明创建失败(内存不够):

c hWin = FRAMEWIN_CreateEx(...); if (!hWin) { GUI_DEBUG_ERROROUT("Failed to create window!"); return; }

  1. 启用内存设备减少闪烁
    在初始化时加上这句:

c WM_SetCreateFlags(WM_CF_MEMDEV);

它会让每个窗口自带“离屏缓冲”,绘制时不直接操作屏幕,大幅降低闪烁。

  1. 监控剩余内存
    开发阶段可以定期打印:

c GUI_ALLOC_GetNumFreeBytes()

如果低于几 KB,就要考虑限制页面数量或释放非活跃页面。

  1. 公共资源统一注册
    字体、图标等不要在每个页面重复加载:

c GUI_SetFont(&GUI_Font32_ASCII); // 在 GUI_Init 后统一设置


高级玩法:模拟“返回栈”,支持多级跳转

如果你要做三级菜单(主页 → 设置 → 时间设置 → 返回 → 返回),怎么办?

可以用一个页面栈(Page Stack)来记录历史:

#define MAX_PAGE_STACK 5 static WM_HWIN hPageStack[MAX_PAGE_STACK]; static int stackIndex = -1; void Page_Push(WM_HWIN hNext) { if (stackIndex < MAX_PAGE_STACK - 1) { hPageStack[++stackIndex] = hNext; } } WM_HWIN Page_Pop(void) { return (stackIndex >= 0) ? hPageStack[stackIndex--] : 0; }

跳转时压栈,返回时出栈:

// 前进 WM_HideWindow(current); Page_Push(current); WM_ShowWindow(next); // 返回 WM_HideWindow(current); WM_HWIN prev = Page_Pop(); if (prev) WM_ShowWindow(prev);

是不是有点像 Android 的 Activity 栈?没错,思想是一样的。


常见问题 & 解决方案(避坑指南)

问题可能原因解法
页面切换后按钮没反应新页面未正确关联父窗口确保控件创建时传入pMsg->hWin作为父窗口
屏幕闪烁严重未启用内存设备加上WM_SetCreateFlags(WM_CF_MEMDEV)
返回后画面错乱多个页面同时显示且层级混乱使用WM_HideWindow隐藏旧页,必要时调WM_BringToTop
内存耗尽崩溃页面反复创建未销毁引入页面管理器,控制实例数量
文字显示乱码字体未正确加载提前注册字体并确保编码匹配(如 GB2312)

还有一个隐藏大坑:触摸校准不准会导致点击无响应
务必确认你的 X/Y 坐标映射正确,否则你以为是 emWin 的锅,其实是驱动没调好。


最后一点思考:emWin 到底强在哪?

很多人觉得 emWin 就是个“画图库”,其实不然。

它真正的价值在于提供了一整套嵌入式 UI 框架能力

  • 分层的窗口管理系统
  • 消息队列与事件分发机制
  • 支持动画、透明度、抗锯齿等高级特性
  • 与 RTOS(如 FreeRTOS)无缝集成
  • 成熟的调试工具链(GUIBuilder、Simulation)

掌握多页面跳转,只是迈出了第一步。后面你还可以做:
- 滑动切换动画(配合GUI_ANIM
- 模态对话框(弹窗确认)
- 动态布局更新
- 主题切换(白天/夜间模式)


如果你正在做一个智能仪表、工业控制面板或者 IoT 设备的人机交互界面,这套方案完全可以直接用上去。

而且你会发现,一旦结构搭好了,加新功能就像搭积木一样简单。

所以别再手动ClearDraw了,学会用 emWin 的窗口机制,让你的嵌入式 GUI 真正“活”起来。

如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。

http://www.jsqmd.com/news/236239/

相关文章:

  • AI健身计划生成:MediaPipe Pose数据分析
  • 批量生成字体图
  • 快速理解es连接工具在热重载中的行为表现
  • 人体姿态检测模型:MediaPipe
  • 一键启动骨骼检测:MediaPipe镜像开箱即用指南
  • 实测MediaPipe骨骼关键点检测:健身动作分析效果惊艳
  • 全面讲解Elasticsearch聚合查询的性能优化策略
  • MediaPipe Pose实战案例:健身动作分析系统优化教程
  • MediaPipe Pose应用开发:集成到现有系统的步骤
  • 从图片到骨骼图:MediaPipe镜像手把手教学
  • MediaPipe Pose部署案例:舞蹈动作分析效果优化实战
  • AI动作捕捉系统:MediaPipe Pose部署与优化实战
  • 人体骨骼检测优化:MediaPipe Pose模型调参详解
  • 零基础玩转人体姿态识别:MediaPipe骨骼检测保姆级教程
  • 基于SpringBoot的闲置资产管理系统(源码+lw+部署文档+讲解等)
  • 人体骨骼检测技术详解:MediaPipe Pose核心算法
  • 一键启动:MediaPipe WebUI镜像让骨骼检测开箱即用
  • 系统学习Packet Tracer汉化界面测试流程
  • 实测MediaPipe骨骼检测:33个关键点精准定位效果展示
  • 一键启动人体骨骼检测:MediaPipe WebUI极速体验
  • 基于SpringBoot的消防知识学习平台系统(源码+lw+部署文档+讲解等)
  • 人体姿态估计实战案例:基于MediaPipe的高精度骨骼检测
  • Proteus8.17安装后无法运行?快速理解修复方法
  • 第一次作业
  • 深度剖析Keil C51界面功能:初学者全面讲解
  • L298N电机驱动原理图解析:适用于智能小车的接线方案
  • AI姿态估计优化指南:MediaPipe CPU极速推理参数详解
  • CPU也能飞!MediaPipe骨骼检测镜像性能优化秘籍
  • 看完就想试!MediaPipe打造的人体动画效果案例展示
  • AI骨骼检测进阶:MediaPipe Pose模型蒸馏技术