文章
关于GUI编程模型(保留式和即时式)
保留式(Retained‐Mode)和即时模式(Immediate‐Mode)UI 在架构设计和渲染流程上有本质区别:
- 渲染模型
- 保留式:UI 框架内部维护一棵“场景图”或“组件树”(widget tree),每个控件(按钮、列表、文本框)都是一个独立对象,框架负责跟踪它们的状态(位置、大小、外观),并在必要时进行重绘。开发者只需要更新状态,框架会自动对比前后状态差异(diff)并高效地重新渲染变化部分。
- 即时式:每一帧(或每次事件循环)都由开发者的绘制代码“刷一遍”整个 UI,框架不会保存上一帧的信息——状态通常由用户管理并在绘制时直接读取。渲染逻辑更像是在“画布”上直接画:要什么控件就画什么,不再有隐式的控件实例。
- 状态管理
- 保留式:状态(例如按钮是否被按下、文本框内容)通常绑定在控件对象内部或通过绑定机制(binding)与业务逻辑相连。更易于做增量更新和动画,因为框架可以精准跟踪哪个控件的哪个属性改变了。
- 即时式:所有状态都由应用代码自己保存(比如一个变量记录按钮状态),在渲染函数里根据这个状态决定绘制样式和行为。状态流动更显式,但需要开发者手动处理许多状态到渲染的映射。
- 编程范式
- 保留式:偏“声明式”风格——你声明“我想要一个按钮、一个输入框、一个列表”,然后在回调里处理事件,框架帮你管理布局和更新。示例:Iced、Druid、Slint、GTK‑rs。
- 即时式:偏“命令式”风格——在每次渲染时你写“先画按钮 A,再画按钮 B,再画文本”,处理输入后再次完整绘制。代码流程更线性,也更灵活。示例:egui、Azul、Dear ImGui(C++)、egui 的 eframe。
- 性能与适用场景
- 保留式:启动时可能需要更复杂的初始化和状态同步,但日常渲染只会更新真正变动的部分,适合控件多、交互复杂、需要丰富原生控件和主题的桌面应用。
- 即时式:每帧都“重画”一遍 UI,但现代计算机和简洁的渲染管线让这种方式也能保持很高的帧率。更方便做动态工具、编辑器、可视化仪表盘,代码简单、迭代快速。
举例对比
| 特性 | 保留式(Iced / Druid) | 即时式(egui / Azul) |
| 渲染方式 | 差分更新,只重绘变更控件 | 每帧全量绘制 |
| 状态管理 | 框架内建,控件自带状态 | 用户代码自管理,渲染函数读取状态 |
| 编程范式 | 更声明式,组件树+回调 | 更命令式,渲染函数+事件处理 |
| 复杂 UI 支持 | 丰富的原生控件、布局、主题 | 需要自己手动布局及样式控制 |
| 典型场景 | 传统桌面应用、表单、工具栏、复杂布局 | 实时工具、游戏编辑器、仪表盘、调试工具 |
选择时,若你需要丰富、原生感强的控件和自动布局,就用保留式;若想快速上手、写少量代码快速迭代界面、或做性能可视化工具,就用即时式。