原本是想从如何创建GacUI的VC++工程开始写的,不过最近网友广泛反映,怎么建工程都能从 Tutorial 里面看明白,就打算说到GacUI的XML资源怎么编译的时候在顺带讲一下。今天主要说的是GacUI的体系架构。html
在构造一个GacUI工程的时候, CppXml 和 MVVM 是两个推荐的Hello World参考项目。虽然还有一些其余的方法,但那并非使用GacUI的正确方法,那些Demo只是为了添加知识作出来的。git
见:http://www.gaclib.net/Document.html#~/ 。这个网站host在Windows Azure上。若是要访问host在github io上面的镜像网站,可使用 http://vczh-libraries.github.io/Document.html#~/ 。程序员
这个网页里面的全部内容其实都在代码头文件的注释里,而后我写了一个工具把他们都抽了出来,作成了这个网页。至于为何不用现成的工具,其实我一开始是直接使用VC++的XML注释功能的。VC++在编译的时候会帮我作完全部的事情,并且在开发的时候还会把注释写进intellisense里面,特别容易使用。github
可是在这个过程当中,我痛苦地发现,VC++要直接生成chm的话,必须使用托管项目。显然GacUI并非托管项目,并且他要求托管项目的缘由,仅仅是为了读取exe/dll里面的元数据。那么Native C++项目的元数据,固然就只能去pdb找了。因而我使用了VC++自带的一个处理pdb读写的COM库,把这个事情给作出来了。设计模式
后来我又痛苦地发现,VC++的XML文档,只给托管项目提供对模板的支持。我把注释写在模板类上,VC++在编译出最后的XML注释汇总文件的时候,写在模板上面的直接没有了!因而我只好放弃使用VC++提供的工具链,转而本身使用C#作了一个C++头文件的parser,从而解决了全部的问题。若是你们感兴趣的话,这个文档生成工具能够在 这里 找到。浏览器
不过我写的文档生成工具,只是产生了一系列的带元数据的XML注释文件。实际上这个网页是另外写的。网页会在访问者点击了一个类的超连接以后,去后台获取相应的XML,而后cache在客户端内存里。因此你点击第二次的话不须要从新加载——直到你关闭了这个网页。安全
为何整个文档只有一个网页呢?由于那个时候我正在学习如何正确使用Javascript和CSS,就顺便练了练手。另外一个缘由是,我想把网站host在github io上,可是github io又不能后台跑程序,因此只好痛苦的使用Javascript把ASP.NET MVC里面我喜欢的部分作了出来,所有逻辑跑在浏览器里。数据结构
能够在两个地方得到GacUI的源文件。架构
第一个是Release。Release的正确下载方法是到 Release repo的Release页面 获取代码。目前Linux版本正在开发,OSX基本已经完工了,不过他们尚未集成到同一个Release repo下面。因此须要到 XGac 和 iGac 两个repo下载代码。app
Windows版本的Release会包含如下几个文件,他们都是成对的.h和.cpp文件:
Vlpp:跨平台的C++基础库。
Workflow:一个能够靠反射访问C++类的脚本引擎,对象模型使用引用计数,跟C++的互操做性无比的好。
GacUI:GacUI的主要部分。
GacUIReflection:GacUI全部能够被脚本访问的类的元数据,用于反射。若是不连接这个文件的话,那么在初始化的时候,构造元数据的过程将被跳过。用户不须要写额外的代码来明确这个过程,只须要选择连接或者不连接这个文件就能够了。
GacUIWindows:GacUI在Windows平台下面的Window Provider和Renderer。
目前你须要使用全部的文件,由于GacUI会把XML资源直接编译成Workflow脚本引擎的字节码,嵌入到生成后的二进制资源文件里。在构造一个窗口的时候,实际上就是在跑脚本。尽管Workflow脚本远没有C++快,可是一个窗口再复杂也复杂不到哪里去,因此加载的时间难以察觉。
明年即将推出把GacUI的XML资源直接编译成C++的选项,到时候能够无限缩小文件体积,不再须要带上GacUIReflection里面的元数据了,那么Workflow和GacUIReflection在大部分状况下就都不须要了。不过若是你的程序想要支持插件,那天然没法使用这个功能。
上面的每个文件都十分的大。我这样作纯粹是为了程序员的使用方便。程序员只须要根据需求连接不一样的文件就能够了,而不须要把整棵目录树都拖进来。不过我本身在开发的时候,显然不可能直接写这些文件的。这些文件是我在作Release的时候,调用我写的一个命令行工具拼装出来的。包括上面提到的文档也是。
因此若是须要阅读GacUI的代码的话,应该分别去 Vlpp 、 Workflow 和 GacUI 三个repo。
事实上GacUI的架构是分层的,从底层到顶层分别是:
Window Provider
Renderers
Elements + Compositions
Controls + Templates
XML Resource Compiler
Window Provider指的是如何操纵操做系统提供的原生的窗口、图片资源、鼠标资源、异步原语和一些其余的东西。毕竟GacUI怎么作,最顶层的窗口是没办法本身作的,最多经过Template来替换掉窗口边框。把GacUI移植到Linux和OSX的工做,主要就是写两个新的Window Provider,而后提供各个平台上不一样渲染器的Renderer。其余的部分都是平台共享的代码。
Renderers跟Window Provider是分开的。毕竟同一个操做系统上你可使用不一样的图像技术来绘图,而不一样的操做系统上你可使用相同的图像技术来绘图。举个例子,OpenGL和Cairo就是在不少平台上能够用的。不过OpenGL在每个平台上都是二等公民,因此我并无真的使用OpenGL来开发。一个GacUI程序在刚开始的时候,若是是Windows的话就是在WinMain函数里,须要首先选择一个Renderer,选择了以后就不能变了。
目前GacUI在不一样的操做系统上使用的绘图技术以下所示:
Windows : GDI, Direct2D 1.0, Direct2D 1.1
Linux : Cairo + Pango
OSX : CoreGraphics + CoreText
Direct2D的1.0和1.1的版本虽然只有初始化的代码有区别,不过这关系到能不能直接跟Direct3D 11.0搅在一块儿,因此单独拿出来说了。目前GacUI在Windows上,若是你选择了使用Direct2D技术:
WinMain.cpp
#include <GacUI.h> #include <Windows.h> int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) { return SetupWindowsDirect2DRenderer(); }
那么在GacUI初始化的时候会优先选择Direct1.1。若是在代码里面引用了GacUIWindows.h,那么你还能够获得每一个窗口所使用的系统相关的对象,可让你在不得不使用非跨平台技术的时候,提供一个机会。
在GacUI里面Element和和Composition分别表明基础的图元和排版功能。每个Element运行在具体的平台的时候,都须要具体的Renderer对象。这些对象是前面两层合做提供的。
Composition是GacUI的其中一个重要部分。这个部分提供了全部的排版功能。一个Composition对象表明了窗口上的一个长方形的区域。每个区域能够嵌入一个Element。当一个Composition肯定了他的位置的时候,那么Element会被填充到整个长方形的区域里面,从而渲染出来。
几乎全部的Element都是很简单的几何图形,除了渲染文字的 GuiColorizedTextElement 和 GuiDocumentElement 。不过在制做控件皮肤(也就是Template的一部分功能)的时候,文本框控件因为功能的复杂性,皮肤须要提供一个区域让控件放置这两个Element,而不是跟普通的控件同样,全权处理全部的渲染对象。
目前Composition支持直接定位、Stack、Table、Flow和一些其余的功能。一个比较特殊的就是,使用 GuiDocumentElement 的 GuiDocumentLabel 和 GuiDocumentViewer 能够在富文本文档的中间嵌入Composition。这个功能是其余的Element所不具有的。所以这两个控件构成了一种新的排版方法。
Element和Composition的具体介绍将在之后的文章中提供。
Control的结构比较复杂。一个典型的GacUI的Control,包含了用来表明控件自己的操做和数据逻辑的Control对象,和包含了如何渲染这个空间的IStyleController或者IStyleProvider对象。IStyleController拥有整个Composition和Element的控制权。若是当一个Control只决定让皮肤控制一部分的Composition和Element的时候,那么他会提供IStyleProvider对象。
不过在开发的时候,程序员不须要区分IStyleController和IStyleProvider,由于使用XML来编写皮肤的时候,都是使用Template来编辑。最后每一个Template会本身去找一个合适的wrapper对象来把本身wrap成IStyleController或者IStyleProvider而后提供给Control。IStyleController / IStyleProvider 和 Template的区别,就在于一个是Pull模型的,一个是Push模型的。Push模型作data binding特别容易,所以在XML里面都是经过建立Template对象来修改一个Control是如何渲染的。
每个Control类都有本身相应的Template类。
对于列表控件、譬如 GuiTextList、GuiListView、GuiTreeView 和 GuiVirtualDataGrid 等,除了Template之外,还有ItemTemplate。Template和ItemTemplate是能够分开指定的。Template确立了整个控件的外观,而ItemTemplate肯定了每个列表项的外观。
若是须要对容器的内容作数据绑定的话,那么须要使用上述4个控件的Bindable版本,分别是它们的子类:GuiBindableTextList、GuiBindableListView、GuiBindableTreeView 和 GuiBindableDataGrid 。在使用这些控件的时候,能够经过在XML里面的Workflow脚本——其实一般就是
<GuiBindableListView ItemSource-eval="ViewModel.Something"/>
这种简单的表达式——把一个C++的容器对象绑定到ListView上。每一个ListViewItem拿到的容器的每个对象,可能最终类型是不同的。GacUI还提供了一个功能,你能够经过给ListView的ItemTemplate指定一系列的Template对象,经过在XML里面写好的这些Template的构造函数的参数的类型,来让ListView决定到底要使用哪一个Template。因而一个异构的列表就这么轻松的造出来了。
大家可能会注意到,Control并不在这一层里面。这是正确的。由于整个窗口就是由Element和Composition共同组成的一张超大的动态矢量图。每个Control负责管理这颗Composition树的一些子树,每个Control会告诉你他最外层的Composition和用来作容器的Composition分别是什么,而后把Control放进Composition、把Composition放进、把Control放进Control的这些动做,实际上都是在操做Composition。在实际的代码里面,你的确也是首选获取Control相应的Composition,而后去操做Composition的。
所以Control和Composition并非平级的,你能够认为Control对于Composition使用了Builder和Facade模式,让你更容易的操做GUI。
固然这种作法对整个GacUI对象的生命周期会有一些影响。当你在C++代码里面delete一个Composition的时候,他会把下面的全部Composition子节点一块儿delete。当你在C++代码里面delete一个Control的时候,他会把下面全部的Control子节点,还有对应的全部Composition一块儿delete。
因此这个时候就会有一个疑问,那delete一个Composition的时候,若是Composition子节点上有Control怎么办?为了解决这个问题,我提供了这样的两个函数:SafeDeleteComposition 和 SafeDeleteControl 。
另外值得一提的是,若是你直接delete一个Control(一般状况下是你用完了一个 GuiWindow 直接把它删掉),他会先删掉整棵Composition树,而后再删除Control树。因此本身开发的Control在析构函数里面,千万不能访问Composition,不然直接GG。
GacUI目前提供的XML资源文件,支持让你构造Window、UserControl、Template、相似CSS那样的InstanceStyle(主要经过XPath来批量设置XML的属性,比选择器好用多了,并且精确控制起来更不费脑)和一些共享的Workflow脚本。共享的Workflow脚本能够用来定义一些窗口的逻辑代码,还有MVVM模式须要的ViewModel的接口和数据结构。
当你准备好一个XML资源的时候,Release里面提供的GacGen.exe会帮你把XML资源编译成一个二进制的资源文件,还有一系列的C++代码。生成的C++代码模拟了C#的partial class的能力,让你能够像Windows Forms同样,准备控件的事件处理,还有在窗口初始化的时候作一些任务等等。并且当你的XML须要更新的时候,GacGen.exe从新生成的C++代码会跟你修改后的那部分自动合并。
使用Workflow脚本写的ViewModel相关的接口和数据结构,也会被一并生成C++代码。在构造一个带有MVVM模式的窗口的时候,你只须要继承一下ViewModel接口,而后把这个类的实例当作窗口的参数填进去就行了。全部生成的代码都是强类型的,若是你对象给错了,会直接没法编译。特别安全。
目前GacUI把全部的用来构造窗口的那部分XML,在编译以后都转成了Workflow脚本的字节码,写进了二进制资源文件里面(这项功能将包含在即将到来的下一个Release里面)。窗口在初始化的时候,会去资源文件里面找到相应的脚原本运行,从而按照要求建立控件和data binding。
在后续的开发过程当中,我还将为XML资源提供Visual State、Animation、State Machine和多语言字符串资源等重要部件。明年还计划让Workflow脚本能够被编译成C++,不只能够大幅度的提升编写出来的GacUI程序的性能,还能够经过让你不再须要连接GacUIReflection,让你的二进制文件的尺寸缩小到1/8。
固然了,仍是会有一小部分状况是没法让你彻底放弃在二进制文件里面带元数据的,举个例子,若是你编写出来的程序须要支持带GUI的插件,那么为了加载那些已经被编译成二进制资源的、在发布了以后用户自行制做的GacUI窗口,那你仍是要保留反射的功能。不过这种需求在广大的GUI程序里面仍是比较罕见的。
值得一提的是,GacUI的data binding的功能十分强大,你可使用任何知足语法要求的Workflow脚本表达式(基本上就像C#同样丰富)来从ViewModel和控件之间作数据绑定。举个简单的例子,你彻底能够写三个文本框,而后让第三个文本框永远等于前两个文本框的数字之和,而且在输入错误的状况下报错:
<SinglelineTextBox ref.Name="textBox1" Text="0"/> <SinglelineTextBox ref.Name="textBox2" Text="0"/> <SinglelineTextBox Text-bind="(cast int textBox1.Text) + (cast int textBox2.Text) ?? '<ERROR>'" />
这个例子要用WPF或者其余GUI框架来写就很蛋疼。那么我在编译XML资源的时候是怎么处理这个表达式的呢?其实这主要使用了语言爱好者们很是熟悉可是老是搞不明白的CPS变换(跟各类语言的玄乎的coroutine在编译的时候其实使用了基本相同的手法),而后把这种pull的代码转变成push的代码,这样就能够在textBox1的TextChanged发生的时候,跟换存起来的其余没有变化的属性的计算后的值(如cast int textBox2.Text)一块儿,作最少的计算,最后写到第三个控件的Text属性里面。
你还能够在这个表达式里面引用你在资源文件里面提供的Workflow脚本,或者干脆引用你本身用C++写的类和库函数,来帮助你作一些不属于ViewModel可是却十分蛋疼的、GUI相关的功能。举个例子,写一个统计学生成绩的程序,你可能须要给学生分优良中差。显然如何描述一个等级,使用中文和英文的方法就不同。然而这并非ViewModel的功能,ViewModel应该只负责计算等级,而后GUI再根据用户使用的系统所提供的语言信息来决定到底要如何显示。
这部分你就能够从ViewModel中间分离开,独立的写成Workflow脚本、XML资源或者C++代码,从而在定义窗口的XML里面使用。这样整个架构分层清晰、测试起来容易、并且需求变动的时候还特别好改。
这篇文章主要介绍了在使用GacUI的过程当中须要了解的一些关于GacUI的体系架构的知识。里面的每个知识点都会陆续在接下来的文章里面详细描述。除此以外,我还会偶尔写一些文章来介绍GacUI的、外部不可见的、跟实现紧密相关的内部架构,以及须要用到的一些编译原理、设计模式等知识,敬请关注。