随着物质生活的丰富,人们的精神生活也越来越丰富。人们闲暇的时间也相对变多,于是很多人就开始寻找打发时间的方法。其中电视便是其中一种非常重要的消遣方式。假如我们打开电视机,看到了一个电视台正在播一部我们之前没看过的,正在一招一式进行打斗的武侠片;另一个电视台正在播一部之前也没看过的,正在重复太极推手的教学片。我想大部分人会选择那部武侠片。为什么?因为那是一个动作体系,不会让人厌烦。而那个推手教学片,可能非常高端,可能非常有内涵,但是总是让人缺乏点新鲜感。我之前更关注技术的细节,而今天开始,我将开始分析一款开源的软件界面库。这系列文章不再拘泥于一些技术细节,而从一个思路的方向去分析这个库。
我介绍这套界面库是目前开源的金山卫士开源计划中的一部分。具体的访问地址是 http://code.ijinshan.com/。其中代码的下载SVN地址是https://openkui.googlecode.com/svn/trunk。我将分析的是版本号为54的版本。(转载请指明出于breaksoftware的csdn博客)
在分析这个库之前,我们可以闭上眼睛,清空大脑,思考一下:如果自己要设计和编写一个界面库,将如何规划和设计?将会遇到什么技术问题?将如何做出一些选择?
首先,我们要做出一个抉择,我们采用窗口控件方式还是采用直接绘制的方式?我们知道windows系统又可称为“视窗系统”,正如其义,我们可以发现,windows系统就是通过一个个窗口展现给我们的。如果使用过SPY++的同学应该发现,windows系统中大部分窗口下的子控件其实也是一些小窗口,只是他们的父窗口被指向了我们看到的那个最最大的最最外层的那个窗口。
如上图中,各个用粗线框起来的部分,就是一个个窗口。这就是问题中所提到的用窗口控件方式。
还有一种方式就是直接绘制,又称为DirectUI。顾名思义,它就是直接在父窗口中绘制各个部分,而不是通过子窗口的形式将各种窗口组成成一个可以协同工作的窗口。最最常见的一个例子便是IE的最最里层那个窗口,它通过其渲染引擎将网页内容绘制在窗口上。这样做有什么好处呢?我们知道,如果我们用控件方式组织网页的话,每个控件都会保存一个句柄,如果一个稍微复杂点的网页,可能有成千上万个元素,也就意味着有成千上万个句柄。这些子窗口还要依赖消息进行窗口管理和绘制。可以想象,这将导致整个网页展现和管理变得非常复杂和庞大。它的执行效率可能连最最差版本的IE都无法比。
那我们选择DirectUI?不,如果我们选择DirectUI,那我在此写这系列文章就没有意义了。而且客户端界面,一般不会有太过于复杂的渲染问题,所以选择窗口控件方式还是可以接受的。如果对内嵌IE式的DirectUI技术赶兴趣的同学可以看两篇相关的博文《如何定制一款12306抢票浏览器——完结篇》和《内嵌IE网页窗口中消除IE默认脚本设置影响的方法》。我这儿就不再赘述。
现在我们确认了使用窗口控件的方式。那我们再抛出第二个问题:使用什么框架?
使用WTL还是MFC?
我相信做windows开发的同学,对MFC很熟悉。我刚毕业的时候,也是看了遍侯捷的《深入浅出MFC》才开始踏上windows开发之路的。但是,之后一直耳闻MFC的种种弊端,其中人们提到最多的一点就是MFC框架复杂容余,编译出来的文件相对较大。于是WTL就进入我们的视野,我曾记得有人给我推荐WTL时,说WTL是微软内部开发用的,从可靠性上来说是没有问题的。但是WTL相对于MFC则要复杂很多,因为你可以发现到处都是模板泛型技术,如果没有一定的C++功底,使用WTL就像云里雾里,非常难受。综合以上分析,我们似乎可以觉得WTL更可以适合我们的开发,因为我们要设计的是一套界面库,我们要设计自己的框架,所以越基础对我们来说是越合适的。
选择好了WTL后,我们来思考下我们这个界面库如何构成?
如何选择描述文件的格式?
自定义一种格式?个人觉得没有必要,毕竟这不是我们界面库的中心问题,我们应该选择一个稳定的,易于表达的格式。可能你会想到HTML,是的,我觉得可以。但是是否我们还可以再精简一点呢?那就是XML了,而且目前已经有开源的XML解析库。我们这样就可以不拘泥XML的细节,专心于其他业务逻辑。
但是有些东西我们还是要考虑的,就是XML内部的属性定义和组织形式。
我们先讨论下组织形式。为了在一开始表述的清晰,我并不准备以XML来讲解,因为其中我们似乎还要探讨我们自定义的XML属性名等问题。为了简化,同时为了贴近我们日常中能遇到的场景,我将使用大家比较熟悉的HTML作为例子。HTML已经为我们定义好了属性和语法,我们将主要从组织形式来思考,并且可以在已有的HTML技术中吸取其发展中产生的优化点。
我们先看一个例子
上面这段包含四张图片的网页,经过我们观察发现,这段代码是非常容余的,可以精简之。比如我们可以将height="200" width="200" 表示为一个class属性,height="300" width="400" 表示为一个class的属性。这样网页就修改为
我们还可以发现有些容余,就是src和atl字段。我们有没有办法将这两个东西简化呢?我对HTML不熟悉,我知识范围内不知道该如何解决这个问题。但是记得曾经做MFC时,在资源文件RC中,有个字符串表(string table),其中保存的是多个字符串键值对。这也是种思路,当然HTML可能不支持这种形式。如此,HTML已经不能满足我们的描述了。我们回到XML来。对于以上的情况,我们可以分为3个XML文件,其中一个用于描述字符串,一个用于描述类型,一个用于描述界面。
http://xxxx.xxx.xx/xx.xx
http://yyyy.yyy.yy/yy.yy
AAAAAA
BBBBBB
CCCCCC
DDDDDD
类型描述
界面描述
这样就清爽很多了。KUI对我之上的设计做了更细的划分,我们将在之后介绍。
有了界面描述文件,下一步就是读取这个文件了。我们大致想象一下这个过程,我们可能需要新建一个结构体,用于描述子控件的属性,举个简单的例子,以下是一个子控件A的描述结构体:
struct StControl{
int x;
int y;
int width;
int heght;
};
因为子控件B内部可能包含多个其他子控件A。于是这种关系可以使用如下结构体表示
struct StControlEx{
StControl stParant;
list ListChildrenControl;
};
而多个子控件B可能又同时组成了另一个窗口控件C,那么就该表示为
struct StControlExEx{
StControl stParant;
list ListChildrenControlEx;
};
那么多个子控件C可能又同时组成另一个窗口控件D,那么就该表示为
struct StControlExExEx{
StControl stParant;
list ListChildrenControlExEx;
};
……
如何通过界面元素属性设置控件?
一般来说,窗口必然会存在以下的属性:
位置:X,Y,Width,Height或者LeftTopX, LeftTopY,RightBottonX,RightBottonY
那么是否我们可以定义如下的结构体和XML
struct StWindow{
int nLeftTopX;
int nLeftTopY;
int nRightBottomX;
int nRightBottomY;
};
对应的,现在我们可以设想下我们可以定义一个基础类CBaseWindow
class CBaseWindow{
public:
void SetCommonAttribute(const StWindow& );
}
继承于该类的类都将具有SetCommonAttribute函数以用于设置这些基础属性。
class CButton: public CBaseWindow{
public:
void SetTextAttribute(const CString& cstrText);
}
再假设我们有个特殊的按钮,那个按钮的文字颜色要是可以指定的,于是我们又要扩展个按钮类出来
class CSpecialButton: public Cbutton{
public:
void SetTextColor(const RGB& );
}
再假设……
SetCommonAttribute(stwindow);
SetTextAttribute(cstrText);
SetTextColor(rgb);
这个还算好的,如果还有更多的属性,那这个调用将非常的没有复杂。怎么解决这样的问题呢?我们将分析KUI库,看看它是如何解决这个问题的。
现在我们再抛出一个问题:如果我们将我们界面描述文件作为独立的文件放在用户的电脑上,可能存在被恶意篡改的可能。还有就是,作为独立的文件,如果其中任何一个文件被破坏了(比如下载失败了),将导致整个界面出现异常。可以见得这样做存在比较大的风险。那么如何解决呢?目前市面上很多软件都将界面描述文件作为资源文件保存在PE文件中,这样PE完好,则界面完好;PE受损,可能程序就不能执行了。而且从技术角度说,修改PE文件的难度比修改XML文件门槛要高些。假如你也认为这是一个好方法,那么坏的问题就来了。一款软件的界面可能需要很多界面描述文件以及图片资源,我们总不能让使用我们界面库的同学,在编译工程时将这些资源文件一个一个加入吧!想想这个也是一个繁琐的问题。程序员最最讨厌重复无聊的工作!那怎么办呢?我们可以让他们将这些资源文件合并成一个文件,一个简单的方法就是将这些文件变成一个压缩包。然后将这个压缩包放到资源文件中。如果你认为这也是个好办法,那么坏的问题又来了。我们如何通过资源文件来使用压缩包中的文件呢?我们可以初步设想下过程:
读取指定资源,将其保存到硬盘(内存)中。
将保存到硬盘(内存)中的压缩包文件解压。
遍历读取解压包中的文件。
那KUI是不是这么做的呢?我们拭目以待。
带着以上这么多选择和问题,我们将在之后的章节中,一一介绍KUI是如何解决问题的,并从中尽量吸取其思想的精髓。