整理硬盘,发现了2012年撰写的内部培训PPT教程。
当时主要从事UI方面的工作,并且负责公司内部培训,因此为了培训相关开发人员,特意编写了一个基于windows 的C++ UI库(当时移动尚未如此之火热,虽然是win32,但是思想是一样的,可以用于任何系统),用于演示UI的核心概念以及面向对象和设计模式相关的内容。
面向对象和设计模式的作用是什么?
我个人认为就是: 通过面向对象,设计模式等思想或手段形成一种机制:封装不变的部分,将可变的部分以虚函数或事件回调等方式公开给调用方。
面向对象教程分为:面向对象需求分析, uml基础及用法,实现一个复杂控件三部分组成(建立在设计模式实现的类库上,等设计模式好了后,再开源)
设计模式教程由六个部分程序组成

UIPLib.lib(核心库)
UI的核心是什么?
我个人将其归纳为: "一个中心,四个基本点"
一个中心: 以树数据结构(控件树)为中心
四个基本点: 控件的布局、控件的事件分发及处理,控件的脏区局部刷新、控件的渲染
控件的布局/事件/刷新/渲染都是建立在控件树不同的遍历基础上的。
由此可知,UIPLib的功能就是实现上述的“一个中心,四个基本点框架“功能。
之所以说是框架,是因为它建立了一套机制,但是实际使用需要从该机制对应的各个基类进行继承实现。具体我们会在后面看到。
将不变的部分封装在类库中,将可变部分声明为虚方法,由继承者来进行override,从而达到天人合一之境。

1. 容器用到的设计模式:适配器/迭代器/策略/装饰
ArrayList.h 中实现了泛型的动态数组,类似std::vector或js中的Array对象
Iterator.h 中主要定义了迭代器模式和线性列表的遍历方向策略(从左到右,从右到左)


Node.h 中定义了基类CNode以及CTypeNode 泛型类,所有子类,请继承自CTypeNode类
NodeIterator.h中定义了树节点线性迭代需要的相关内容




之所以花这么多力气来解释树的遍历,是因为其重要性,树结构可以说是我用的最多,最强大的数据结构。一定要掌握他!
在UIPText.exe程序中,进行了8个迭代器的测试:


2. ControlSystem用到的设计模式:组合及模板方法
在这个部分中,使用了结构型设计模式-组合(其实树结构就是经典的组合模式)以及模板方法的模式。
通过ControlSystem系统,建立一个自定义控件的插件体系
后面看到的UIPControl.lib就是具体控件的实现,而UIPLib中的ControlSystem定义的是共性部分。典型的插件方式。通过ControlSystem提供CControlBase基类,你可以继承CControlBase并实现自己的控件,然后可以将一个控件组成一个插件lib,或者一系列控件组成一个插件lib,进行链接导入,灵活性大增,后面会了解如何使用的。
-
ControlManager是Control的根节点,也是控件布局/事件分发/局部刷新/渲染的起始节点!!
3. 事件系统用到的设计模式: 观察者 使用开源的FastDelegate库,并在FastDelegate基础上增加了多播功能(实际就是实现了C#的delegate功能),并支持触发顺序的调整(其实该实现源码来自于Torque3D Engine)
和C# 事件类似的系统,对于自定义事件,只需如下:

定义好三者,注册事件处理函数,可以在任意地方触发事件,在任意地方,以全局函数,成员方法或静态方法等方式进行回调,超级好用的类C#事件系统
这里用到的观察者模式和书上的经典描述有差别,因此在UIPTest.exe中实现了一个经典版的观察者模式,可以调用看看结果。并且在教程中分析了Java awt中的事件监听方式。
对C# delegate事件体系的扩展:Document Object Model (DOM) Level 3 Events Specification
关于该事件系统(也就是经典的冒泡事件系统),我是非常喜欢的。因此在后来,我花了点时间,研究webkit相关源码,从中剥离出干净代码移植到的代码中。
其实DOM Event事件系统依赖如下几个特点:
- 需要一个树结构----已经具有控件树了
- 需要事件监听优先级----在FastDelegate基础上实现了根据权重调整触发的先后顺序
- DOM Event事件支持多播触发----在FastDelegate基础上实现了多播功能
- 冒泡与和捕抓----需要自己实现,我们只要实现这个就可以了
其实冒泡事件的使用是很有技巧的,用的好的话,是很令人愉悦的体验。
关于冒泡事件,并没有在本系列教程中,如有需要就自己扩展一下
4. 渲染系统用到的设计模式:工厂方法

- 面向接口编程,使用工厂方法返回接口
- 实现了GDI 渲染器,很容易扩展到其他渲染backend.
- 将结果渲染到IRenderImage中,然后bitblt到显存,通过这种方式(双缓存)避免闪烁
- 使用渲染到纹理(IRenderImage),有利于应用于3DAPI中,例如opengl/directX。
5.脏区局部刷新用到的设计模式: 模板方法
- 因为使用windows,所以使用了windows自有的InvalidateRect API进行脏区标记,用于触发重绘
- 如果使用各自系统的控件体系,基本都有自己的脏区标记函数,例如android中的invalidate方法,ios中的setNeedsDisplayInRect方法,这些API都可以指定一个rect,需要重绘的地方限制在该rect中。
- 关于脏区的使用:

- CControlBase的Render方法规定了调用的流程:

- 由此可见,子类override OnXXXRender时,必须要先调用基类相同名称的方法才OK。
例如UIPControl.lib中自定义的控件的OnRender方法的override必须要要调用基类同名方法

关于脏区重绘,是个很好玩的领域,是2D游戏或UI引擎性能提高的关键要素。在这个部分,我有过一定的研究,并且在opengl/directX上实现了一个效率极高的脏区刷新及渲染引擎。实际上,gl/dx这种图形api,通过修改投影矩阵,我们可以在顶点处理阶段就进行裁剪,将渲染效率大幅度提高。、这部分涉及到gl/dx的渲染流水线以及数学相关知识。在完成本文档后再深入的了解一些比较好玩的内容。
-
关于控件的布局,包括控件的尺寸和位置计算,是最复杂的一个部分,在我们现在的引擎中,并没有实现具体的布局引擎,关于布局,在完成本文档后再深入的了解布局相关内容。 说实话,布局算法太多了,这个需要深入思考后,具体一一描述了。每个算法背后都是有优缺点。
7. UIPLib的入口类CApplication用到的设计模式:单例和模板方法 init模板方法,初始化各个子系统

鼠标和键盘事件的分发
RunLoop
CApplication本身是单例模式,支持游戏模式(独占模式,cpu 100%)和UI模式
UIPControl.lib库(实现两个在后面程序中要用到的控件)

UIPIconButton 用来显示带Icon的Button,演示了如何增加事件以及渲染
UIPaintArea 与Canvas类似,规定了一个显示区,并演示了CRenderEvent事件如何使用

UIPAnimation.exe (以UIPLib/UIPControl为基础,实现一个贝塞尔路径跟随动画演示效果)


用到的设计模式:
ISprite使用桥接模式实现
//动画精灵接口,桥接模式
//桥接模式的核心是接口与实现分离
//也就是面向接口编程工厂方法(使用std::shared_ptr防止内存泄露)

- CPathFollower类使用flyWeight模式

- CAnimationController使用备忘录模式,用于实现Record/Replay功能
UIPPaint.exe (以UIPLib/UIPControl为基础,实现一个简单的绘图程序)


- CDrawerVisitor类使用了Vistor模式,用于渲染辅助功能
- 使用Command模式实现了Undo/Redo功能,在程序中你可以使用鼠标中键来进行Undo,使用鼠标右键来进行Redo.或者使用ctrl+z进行Undo,ctrl+y进行Redo.
- ResponsibleChain系统使用职责链模式实现了一个帮助系统,程序中你按F1,会根据当前的控件焦点显示帮助内容
- 程序中各个图标的切换随之功能切换(例如按圆形就绘制圆圈),使用状态机模式来管理,整个操作就非常清晰明了,特别棒
- CFacadeMediator类是整个核心结构,即实现了门面模式,又实现了中间者模式,他是大内总管,所有的事情都需要到这个类来中转。






具体细节和源码,这几天会全部出来,毕竟都是现成的PPT,我就偷懒直接截图黏贴好了。分如下三篇:
CoreLib篇
Control/Animation篇
Paint篇
2017/8/15日更新说明:
关于此系列 原本4篇发布的续集目前撤回关闭。
因为:
- 原本是win32 gdi实现,目前改成c++ opengles实现(使用nanovg库),跨平台,可以运行在ios/android/linux/windows上
- js作为上层语言进行调用
- 目前实现的是C# delegate类似事件回调,在此基础上再实现如下两个事件系统:
冒泡事件系统Document Object Model (DOM) Level 3 Events Specification【已实现】
职责链事件系统(ios UIResponser)
关于三大js引擎(ms chakra core/google v8/mozilla spidemonkey 技术选型过程)以及跨平台渲染引擎(cairo/google skia/nanovg技术选型过程)记录在如下几篇周记中(还记录了其他一些东西)。目前理论基础已经可行性验证都已通过!