Welcome

首页 / 软件开发 / 数据结构与算法 / GacUI与设计模式(一)前言

GacUI与设计模式(一)前言2014-10-15 cnblogs 陈梓瀚说起GacUI(http://www.gaclib.net/,gac.codeplex.com),其实这个想法在我还在上大三的时候就已经有了。但是由于经验不足,在当时并没能够把这个东西给做出来,直到去年(2011)的国庆节为止。想想到现在也做了快一年了,GacUI也可以用来写一些不是特别残暴的C++GUI程序了。前几天有人问道,为什么在PC都快完蛋了并且大部分GUI都已经用C#来做的时候,我还要做这个东西呢?其实,这有两个原因:第一个我喜欢折腾C++;第二个C++好像也没什么特别好的GUI,因此也想尝试一下,如果做成了就维护下去,做不成了好歹还可以提高自己的水平,总之是不会浪费时间的。所以我就在想,GacUI写到现在也快一年了,并且我最近也看到cppblog上面有几个人也想搞搞GUI,因此我想把GacUI的一些设计思想,和我得到这些思想的过程写出来,顺便也介绍一下GacUI的架构,让一些有兴趣的人(特别是装配脑袋)也可以来折腾折腾。

GacUI的架构的最重要一点就是要跨平台。当然这不一定意味着我将来一定会把GacUI移植到别的什么操作系统去,但至少Windows的Classic Desktop和Metro的两套API就毫无相似之处,同时搞定他们,也算是跨平台了。而且就算是基于同一种API,上面还有不同的渲染器的API,譬如说GDI,譬如说Direct2D,他们也是截然不同。GacUI的设计至少要可以屏蔽掉他们的区别。当然,这在技术上有一个很好的方法来保证,就是GacUIIncludes.h里面不包含Windows.h的任何内容——因此至少在头文件里面,所有的东西都是跟Windows无关的。当然在非GUI的部分,我们还是需要Windows.h的,并且有些人喜欢对GacUI做点hack的操作,因此我还是在GacUI.h里面提供了几个额外的依赖于Windows.h的函数来暴露一些内部细节。那这样如何跨Classic Desktop和Metro呢?有一个简单的方法,就是可以在编译的时候给些宏开关,譬如说GACUI_WINDOWS_CLASSIC_DESKTOP(缺省)或者GACUI_WINDOWS_METRO之类的东西,来屏蔽掉不需要的部分。当然这部分在移植到Metro之前我不会加进去。

基于这个想法,如果大家阅读了GacUI的代码的话,会发现在文件LibrariesGacUISourceNativeWindowGuiNativeWindow.h里面定义了一个INativeController接口,而且目前只有Windows Classic Desktop一个实现。INativeController的内容很多,提供了跟具体的平台有关的操作,譬如说读写图片文件啦、创建消灭窗口啦、显示器操作啦、还有各种其他的输入输出等等。实现一个从头INativeController还是比较繁琐的,因为GUI这种对操作系统重度依赖的东西,想剥离开来,就会发现他依赖了一大坨API。这也解释了为什么INativeController的各个XXXService函数返回的对象的方法的总和有上百个。不过从Classic Desktop移植到Metro还是相对比较简单的,因为大部分内容还是可以共享的。

其次就是渲染器了。渲染器跟平台是交叉依赖的。譬如说OpenGL在linux上和Classic Desktop上都可以用,Direct2D在Classic Desktop上和Metro上都可以用,GDI只能在Classic Desktop上面用。因此这就是为什么我最终没有把渲染器也写在INativeController里面,而是把渲染器整个给屏蔽掉了,根本没有在GacUIIncludes.h里面给出他的接口。但是考虑到GacUI是一个支持换肤的GUI库,因此肯定需要让皮肤来自己决定如何绘图。后来我就想了一个办法,把渲染器的结构整个拿掉,替换成各种各样的图元(IGuiGraphicsElement)。所谓的图元就是类似于方形啊,圆形啊,填充啊,渐变啊,文字之类的东西。皮肤自己把图元按照一定的排版关系(在下文中有描述)拼装好,然后GacUI内部的一个小系统会利用Bridge和Abstract Factory两个模式的结合体(参考LibrariesGacUISourceGraphicsElementGuiGraphicsElement.h)来为这些图元分配好渲染器对象(IGuiGraphicsRenderer)。然后图元和渲染器之间用了Listener模式在交换信息。这样的好处是,当图元受到改动的时候,这个图元对象的专用渲染器对象可以选择cache一些信息,然后在窗口渲染的时候,只需要访问所有的渲染器对象(在排版对象GuiGraphicsComposition的组合项形成了一棵树),让他们渲染自己就可以了。