3. TouchGFX 架构解析
在嵌入式 GUI 开发中,界面逻辑与业务逻辑一旦耦合,代码就会迅速变得难以维护。TouchGFX 采用 MVP(Model-View-Presenter)模式来组织应用,把“数据从哪来”“界面怎么画”“用户操作如何响应”三件事拆开,使界面刷新与硬件、RTOS、业务状态解耦。理解 MVP 的职责边界与数据流,是后续使用 TouchGFX Designer、编写 Screen 类与处理交互事件的基础。
一、MVP 三层的核心职责
TouchGFX 的 MVP 并不是一个抽象口号,而是直接映射到代码结构中的三类对象。每个 Screen 通常由一对 View 与 Presenter 组成,Model 则是全局单例,供所有 Presenter 访问。
- Model:应用的数据与业务状态中心。它保存温度、电量、模式、列表数据等,负责与底层驱动、RTOS 队列、传感器或通信模块交互。Model 不关心任何控件,也不直接调用
invalidate()。 - View:负责“长什么样”和“怎么画”。它持有 TextArea、Button、Box 等控件,处理底层绘制与触摸区域,但不决定业务规则。View 通过
presenter->…把用户操作上报。 - Presenter:连接 Model 与 View 的协调者。它从 Model 取数据,转换成 View 能显示的格式,调用 View 的更新接口;同时接收 View 上报的事件,决定调用 Model 的哪个业务方法。
一句话记忆:Model 管“数据与规则”,View 管“像素与触摸”,Presenter 管“翻译与调度”。
二、数据流与调用方向
TouchGFX 的数据流是双向的,但方向被严格约束:View 不直接读写 Model,Model 也不直接操作 View。所有跨层通信都经过 Presenter。典型流程如下:
- 用户点击“开始测量”按钮,View 的
handleClickEvent或绑定回调触发。 - View 调用
presenter->startMeasure(),把意图交给 Presenter。 - Presenter 调用
model->startMeasure(),由 Model 启动采集或发送命令。 - Model 更新内部状态,例如
measureState = RUNNING。 - Presenter 在
tick()或回调中读取 Model 状态,调用view.setMeasureState(...)。 - View 更新控件文本、颜色或进度条,并调用
invalidate()请求重绘。
这个链条中,Presenter 是唯一同时持有 Model 与 View 引用的角色。它不直接绘制像素,也不直接访问硬件寄存器,从而保证界面代码可以在模拟器中独立运行。
三、代码结构中的 MVP 映射
在 TouchGFX Designer 生成的项目中,每个 Screen 通常包含 Screen1View 与 Screen1Presenter,而 Model 是全局单例。下面是一个简化的温度显示场景:
// Model.hpp
class Model {
public:
void setTemperature(float t) { temperature = t; }
float getTemperature() const { return temperature; }
private:
float temperature = 0.0f;
};
// Screen1Presenter.hpp
class Screen1Presenter : public touchgfx::Presenter {
public:
void onTemperatureUpdated(float t) {
view.setTemperatureText(t);
}
void onStartClicked() {
model->startMeasure();
}
private:
Screen1View& view;
};
// Screen1View.hpp
class Screen1View : public touchgfx::View<Screen1Presenter> {
public:
void setTemperatureText(float t) {
Unicode::snprintf(tempBuffer, TEMP_SIZE, "%.1f C", t);
tempText.invalidate();
}
void buttonCallback() {
presenter->onStartClicked();
}
};
可以看到,View 只做字符串格式化与 invalidate(),Presenter 只做转发与转换,Model 只保存数据。三者各司其职,界面代码可以在 PC 模拟器中运行,而 Model 可以替换为真实硬件驱动。
四、典型场景:温度采集与界面刷新
假设系统每 500 ms 采集一次温度,并在界面上显示。数据流如下:
| 步骤 | 触发者 | 动作 | 涉及对象 |
|---|---|---|---|
| 1 | 定时器 / RTOS 任务 | 读取 ADC,得到 26.5°C | Model |
| 2 | Model | 更新内部 temperature 字段 | Model |
| 3 | Presenter::tick() | 读取 model->getTemperature() | Presenter → Model |
| 4 | Presenter | 调用 view.setTemperatureText(26.5) | Presenter → View |
| 5 | View | 更新 TextArea 并 invalidate() | View |
| 6 | TouchGFX 引擎 | 重绘脏区域,显示 26.5 C | 框架 |
如果用户点击“切换单位”按钮,流程则反向:View 捕获点击 → 调用 presenter->toggleUnit() → Presenter 修改 Model 中的单位标志 → 下一次 tick() 时 Presenter 读取新标志并更新 View 文本。整个过程中,View 从未直接访问 Model。
五、常见误区与复盘
误区一:在 View 中直接调用 Model。这会让界面代码依赖硬件或全局状态,模拟器无法独立运行,单元测试也难以编写。正确做法是让 View 只调用 Presenter。
误区二:在 Model 中调用 invalidate()。Model 不应知道任何控件或绘制细节。刷新应由 Presenter 在合适的时机驱动 View 完成。
误区三:把所有逻辑都塞进 Presenter。Presenter 应保持“薄”,只做数据转换与事件转发。复杂业务规则应下沉到 Model,避免 Presenter 膨胀成新的“上帝类”。
复盘建议:检查每个 Screen 的 View 类,确认其中没有出现 model-> 调用;检查 Model 类,确认其中没有出现 invalidate() 或控件类型。只要这两条守住,MVP 的边界就基本清晰。
掌握 MVP 的职责划分与数据流后,后续在 TouchGFX Designer 中拖拽控件、绑定交互、编写 tick() 刷新逻辑时,就能清楚地知道每一行代码应该放在哪一层,从而构建出可维护、可测试、可移植的触摸交互系统。