谈论用c+写gui框架

好久没用c+写代码啦
我喜欢用C++写 GUI 框架,因为那种成就感是实实在在地能看到的。从毕业到现在写了好多个了,都是实验性质的。什么拳脚飞刀毒暗器,激光核能反物质,不论是旁门左道的阴暗伎俩,还是名门正派的高明手段,只要是 C++ 里有的技术都试过了。这当中接触过很多底层或是高级的技术,像编译时类型检测,运行时代码修改等等,按实现的不同 GUI 涉及的东西是没有边际的。从最开始模仿 MFC,ATL 那样的实现学到很多东西,然后开始看一些开源的著名的 GUI 框架,像 MFC,WTL,SmartWin++,win32gui,jlib2 ,VCF 获得很多启发,到现在似乎有一种已看尽天下 GUI 的感觉。在学习别人的框架和自己的实现过程中,真真实实地感觉自己成长了不少,也有很多感悟。

  写到这,我作为轮子制造爱好者,在这里向那些喊着"不要重复制造轮子的"批评家们承认错误。在有那么多好的轮子的情况下,我不值得浪费地球资源,浪费时间精力来自己手工重复打造。但是不值得归不值得,在值得和喜欢之间我还是选择后者。并且人生在世,什么才是值得?我觉得不是拯救人类,为世界和平做贡献,也不是努力奋斗,为地球人民谋福利,而是简单地做自己喜欢的事。

  写过的那些代码很多都消失在硬盘的海洋里了,但那些挑灯苦想来的感悟还在。在它们也消失之前,我想利用空闲时间把这些觉得有点用处的经验写出来,正好这个博客也已经快一年没更新了。另外也算是对那些发我邮件的朋友的回应。

  我的想法是用一系列日志,按照实现一个 GUI 框架的具体思维递进过程来阐述实现一个 GUI 框架的具体思维递进过程。这样说好像有点递归,简单地解释就是这一系列日志不是想用《记忆碎片》那样错乱的叙述方式来说明一个多有意思的故事,而是尽量简单自然地记录一下写 GUI 框架过程中我的思考。这个递进过程也就是实现一个 GUI 框架的过程,一系列日志之后,我们将会看到一个长得漂亮眼,极富弹性,能干又节约的 GUI 框架。

  虽然写的内容都是在 Windows 的 GUI 系统之上,但其原理是触类旁通的,其它基于消息的 GUI 系统也都大同小异。所用的代码也都是阐述原理的,自知绝对达不到商业巨作的水准,所以请不要一上来就批判,要知道我只是想分享而已。之所以先这样说一下,是很害怕那种一上来就"怎么不跨平台啊?“,“怎么都还看得到HWND啊?“,“怎么不能用成员函数处理消息啊?“的同志。不喜欢站在高处指着别人的天灵盖说话的人。要知道车轮也是一步步造出来的,不要一开始就想载着MM在高速路上飙豪车像少年啦飞驰。

  我认为写技术博客有三种境界,一种是一直在那绘声绘色地描述自己的鱼有多可口多美味,让读者只能垂涎兴叹,一种是授人以鱼的人,闷头就摆出来各种生猛海鲜,让读者难以消化,还有一种境界是授人以渔,怎么钓鱼怎么煮鱼都细细地教给读者。读博客的人有两个境界,一种是只吃鱼的,一上来就只要代码,一种是学打鱼的,想知其然更想知其所以然。读博客时我努力做学打鱼的类型,自己写博客时我会努力做到授人以渔的境界。

  另外要说明的是,同样作为尘世中的一个渺小个体,我大多数时候也是在为生存而奔波劳累着的。除此之外剩余的大多时候,更是要玩游戏,K歌,看电影,陪MM,吃喝玩乐。再剩余用来写这个的时候不是很多,有可能这一系列日志一夜写就,也有可能增删五年披阅十载,孩子都叫爸了还没完成。所以请大家不要对这个博客抱很大的期待,就当我是路边街头的表演,你打酱油经过时偶尔瞟过来一眼就好了。

  要说的废话终于说完了,下面开始正题。

2 基本概念

  基于消息的 GUI 框架的封装,一切都围绕消息展开。复杂的框架设计,明确了需求之后,第一步首先是划分模块。所以,要阐述一个设计过程,第一步也应该是先说清最基本的概念和模块划分,而不是一上来就用广义相对论把读者全部放倒。GUI 框架是干什么的当然是地球人都知道的,但 GUI 框架没有什么已经划分的标准概念,我是按照设计的需要来划分的。如果把 GUI 框架看作一个单位,那么这个单位里最重要的角色有这几个:

?消息发送者(message sender)
?消息监听者(message listener)
?消息检查者(message checker)
?消息处理者(message handler)
?消息分解者(message cracker)
?消息映射者(message mapper)

  下面分别说明。

2.1 消息发送者和消息(message sender,message) 

  消息发送者其实只是在这里友情客串一下,它不在框架设计之内,由操作系统扮演这个劳苦功高的角色,它的工作是将消息发送到消息监听者。在这里面隐含了一下最重要的角色,消息。其实剩余的所有角色说到底也只是死跑龙套的,真正领衔的是消息本身,比如窗口大小改变了的消息,按钮被点击了的消息等等,所有人都高举旗帜紧密团结在它周围进行工作。但消息本身只是一个很简单的数据结构,因为再复杂的 GUI 系统,它的消息也不过是几个参数,所以框架的实现重点在其它的角色。在此之前简单地封装一下消息,一个最简单的封装可能是这样:

1: // 消息封装类

2: class Message

3: {

4: public:

5:     Message( UINT id_=0,WPARAM wparam_=0,LPARAM lparam_=0 )

6:         :id( id_ )

7:         ,wparam ( wparam_ )

8:         ,lparam ( lparam_ )

9:         ,result ( 0 )

10: {}

11:

12: UINT id;

13: WPARAM wparam;

14: LPARAM lparam;

15: LRESULT result;

16: };

  就这样的我们的公司已经有了核心角色了。从概念上讲,我们的这个基于消息的 GUI 框架已经完成了 99% 。然后我们可以以它为中心,按功能划分进行详细讨论,一步步完成那剩余的 1% 的极富创意和挑战的工作。在此之前,先得简单解释一下这几个角色都各是什么概念。消息传送者如上所述,将不在讨论范围内。

2.2 消息监听者(message listener) 

  消息监听者完成的工作是从操作系统接收到消息,消息是从这里真正到达了框架之内。最简单的消息监听者是一个提供给操作系统的回调函数,比如在 Windows 平台上这个函数的样子是这样:

1: //我是最质朴的消息接收者

2: LRESULT CALLBACK windowProc( HWND window,UINT id,WPARAM wparam,LPARAM lparam );

  一个好 GUI 框架当然不能赤祼祼地使用这个东西,我们要在此之上进行面向对象的封装。消息监听者能想到的最自然的封装模式是观察者模式(Observer),这样的模式下的监听者实现看起来像这个样子:

1: //我是一个漂亮的观察者模式的消息监听者

2: class MessageListener

3: {

4: public:

5:     virtual LRESULT onMessage( Message* message ) = 0;

6: };

7:

8: //监听者这样工作

9: MessageListener* listener;

10: window->addListener( listener );

11:

  jlib2 和 VCF 的实现就是这种模式。但现实当中大多数框架没有使用这种模式,比如 SmartWin++ 和 win32gui ,甚至没有使用任何模式比如 MFC 和 WTL 。我想它们所以不采用观察者模式,有些是因为框架整体实现的牵制,有的则可能是因为没能解决某些技术问题。我们的 GUI 框架将实现观察者模式的消息监听者,所以这些问题我们后面也会遇到,到时候再详述。

2.3 消息检查者(message checker)

  消息检查者完成的工作很简单。当收到消息的时候,框架调用消息检查者检查这个消息是否符合某种条件,如果符合,则框架再调用消息处理者来处理这个消息,所以有点类似一个转换者,输入(消息),输出一个(是/否)的值。最简单的检查者可能就是一个消息值的比较,比如:

1:

2: /最简单的消息检查者

3: essage.id == /*消息值*/

4:

5: /比如

6: essage.id == WM_CREATE

  展开MFC 和 ATL 的消息映射宏,可以看到它们的消息检查就是用堆积起来的消息值比较语句完成。这就是消息检查者最原始最自然最简单的实现方式,但这种方式缺陷太多。我们的框架将实现一个自动化,具有扩展性的消息检查者,后文详细讨论。

2.4 消息处理者(message handler)

  消息处理者是我们最终的目的。GUI 框架所做的一切努力都只是前期的准备,直到消息处理者运行起来那一刻,整个公司才算是真正地运转起来了。消息处理者的具体实现可能是自由函数,成员函数或者其它可调用体,甚至可以是外部脚本,处理完毕可能需要给操作系统返回一个结果。最简单的消息处理者可以就是条语句,比如:

1: //消息处理

2: alert( "窗口创建成功了!" );

3:

4: //返回结果

5: message.result = TRUE;

  上面代码中"显示消息框"的动作就是一个消息处理,以上两行代码可视为消息处理者。最常见的消息处理者是函数,比如:

1: //消息处理

2: _handleCreated( message );

  代码中的函数 _handleCreated 就是一个典型的消息处理者。消息处理者的实现难处在于,既要支持多样性的调用接口,又要支持统一的处理方式。我们的框架将实现一个支持自由函数,成员函数,函数对象,或者其它可调用体的消息处理者,并且这些可调用体可以具有不同参数列表。后文将进行消息处理者的详细讨论。

  在这里有必要再说明一下。一个判断语句的大括号之前(判断部分)是消息检查的动作,大括号之内(执行部分)是实际的消息处理。因此一个判断语句虽简单,却包含消息检查者和消息处理者,以及另外一个神秘的部分(见后文),一共三个部分。代码像这样:

1: if ( //消息检查者 )

2: {

3:     //消息处理者

4: }

  比如下面的代码:

1: // message.id == WM_CREATE 是消息检查者

2: // _handleCreated( message )是消息处理者

3:

4: if ( message.id == WM_CREATE )

5: {

6:     _handleCreated( message );

7: }

8:

2.5 消息分解者(message cracker)

  消息分解者是为消息处理者服务的。不同的消息处理者需要的信息肯定不一样,比如一个绘制消息(WM_PAINT)的消息处理者可能需要的是一个图形设备的上下文句柄(HDC),而一个按钮点击消息(BN_CLICK)的消息处理者则可能需要的是按钮的ID,它们都不想看到一个赤祼祼的消息杵在那里。从消息中分解出消息携带的具体信息,这就是消息分解者的工作。最简单的消息分解者可能是一个强制转换,比如:

1: // WM_CREATE 消息参数分解

2: CREATESTRUCT* createStruct = (CREATESTRUCT*)message.lparam;

3:

4: // WM_SIZE 消息参数分解

5: long width  = LOWORD( message.lparam );

6: long height = HIWORD( message.lparam );

  上面的的代码虽然简单但 100% 完成了消息分解的任务,所以它也是合格的消息分解者。我的框架将实现一个自动化,可扩展的消息分解者。后文将以此为目标进行详细讨论。

2.6 消息映射者(message mapper)

  消息映射者是最直接与框架外部打交道的部分,顾名思义,它的工作就是负责将消息检查者与消息处理者映射起来。最简单的映射者可以是一条判断语句,这个判断语句,如代码所示:

1: // if 语句的框架就是一个消息映射者

2:

3: // 消息映射者

4: if ( /*消息检查者*/ )

5: {

6:     /*消息处理者*/

7: }

1: // if 语句将消息检查者 message.id==WM_CREATE 和消息处理者 _handleCreated(message) 联系起来了

2: if ( message.id == WM_CREATE )

3: {

4:     _handleCreated( message );

5: }

  上面的代码 的if 语句中,判断的部分是消息检查者,执行的部分是消息处理者。if 语句把这两个部分组成了一个映射,这是最简单的消息映射者。到这里可以发现,这个简单的 if 语句有多不简单。它低调谦逊但独自地完成了很多工作,就像公司的小张既要写程序,又要扫地倒茶,还义务地给女同事讲笑话。MFC 和 WTL 的消息映射宏展开就是这样的 if 语句。像 jlib2 那样的框架,虽然处理者都虚函数,但在底层也是用 if 语句判断消息然后来进行调用的。当然还有华丽一点的消息映射者,像这样:

1: // 华丽一点的消息映射者

2: window.onCreated( &_handledCreated );

  这个 onCreated 也是一个消息映射者,在它的内部把 WM_CREAE 消息和 _handleCreated 函数映射到一起,这种方式最有弹性,但实现起来也比宏和虚函数都要困难得多。SmarWin++ 就是使用的这种方式,它的消息映射者版本看起来一样的阳光帅气,但内部实现有些细节稍嫌猥琐。我们的 GUI 框架将实现一个看起来更美,用起来很爽的消息映射者像这个样子:

1: // 将消息处理者列表清空,设置为某个处理者

2: // 可以这样

3: window.onCreated  = &_handleCreated;

4: // 或者这样

5: window.onCreated.add( &_handleCreated );

6:

7: // 在消息处理者列表中添加一个处理者

8: // 可以这样

9: window.onCreated += &_handleCreated;

10: // 或者这样

11: window.onCreated.add( &_handleCreated );

12:

13: // 清空消息处理者列表

14: // 可以这样

15: window.onCreated –;

16: // 或者这样

17: window.onCreated.clear();

  值得说一下,这种神奇的映射者是接近零成本的,它没有数据成员没有虚函数什么都没有,就是一个简单的空对象。就像传说中的工作能力超强,但却不拿工资,不泡公司MM,甚至午间盒饭也不要的理想职员。在后文当中会具体详述这个消息映射者的实现。

3 结尾废话

  到目前为止我们的框架已经完成了 99% 。下篇准备开始写最简单的消息检查者,但说实话我也不知道下一篇什么时候开始。看看上一篇日志,竟然是一年前写的,这一年内发生的事情很多,但自己浑浑噩噩地的好像一眨眼就到了现在,看着 CPPBLOG 上的好多其它兄弟出的很多很有水准的东西,心里真是惭愧。昨天看了《2012》,现在心里还残留有那种全世界在一间瞬间灰飞烟灭的震撼,2012年也不远了,我也赶紧在地球毁灭之前加把油把这些日志写完了吧。代码难写。

回避的是虚函数。举个例子
class Base
{
public void NeedToInvokeOnDraw()
{
((Derived*)this)->OnDraw();
}
public void OnDraw(){}//空实现
}

class Derived1 : public Base
{
public void OnDraw(){}//我有新OnDraw
}

class Derived2 : public Base
{
//我用旧OnDraw
}
执行代码编译吧

时间: 2024-07-28 21:39:27

谈论用c+写gui框架的相关文章

Android开发之手把手教你写ButterKnife框架(二)

欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开发之手把手教你写ButterKnife框架(一)我们讲了ButterKnife是什么.ButterKnife的作用和功能介绍以及ButterKnife的实现原理. 本篇博客主要讲在android studio中如何使用apt. 一.新建个项目, 然后创建一个module名叫processor 新建m

【转载】如何写一个框架:步骤(下)

说明:写本文的时候作者完全是把脑子里的东西写了出来,没有参考任何的资料,所以对于每一项内容可能都是不完整的,不能作为一个完整的参考.有一些方法学的东西每个人都有自己的喜好,没有觉得的对和错. 单元测试 在这之前我们写的框架只能说是一个在最基本的情况下可以使用的框架,作为一个框架我们无法预测开发人员将来会怎么使用它,所以我们需要做大量的工作来确保框架不但各种功能都是正确的,而且还是健壮的.写应用系统的代码,大多数项目是不会去写单元测试的,原因很多: 项目赶时间,连做一些输入验证都没时间搞,哪里有时

从零开始写JavaWeb框架(第一章节)

今天买的两本书到了,其中一本是<从零开始写JavaWeb框架> 因为是第一次用IDEA,期间遇到很多问题,比如:怎么在IDEA中配置tomcat,我是这样解决的: 在IDEA界面的右上角点击: 点击+,选择Maven 到了如下界面: 在Name中输入tomcat,在Command line中输入tomcat7:run,然后点击apply. 然后就可以运行了. 第一章节如下: pom.xml <?xml version="1.0" encoding="UTF-

Android开发之手把手教你写ButterKnife框架(三)

欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了,如何在android studio使用apt < Android开发之手把手教你写ButterKnife框架(二)> 然后在Processor里生成自己的代码,把要输出的类,通过StringBuilder拼接字符串,然后输出. try { // write the file JavaFileObj

写一个框架的详细步骤

定位 所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等. 如果你打算写框架,那么肯定心里已经有一个初步的定位,比如它是一个缓存框架.Web MVC框架.IOC框架.ORM/数据访问框架.RPC框架或是一个用于Web开发的全栈式框架. 是 否要重复造轮子?除非是练手项目,一般我们是有了解决不了问题的时候才会考虑不使用既有的成熟的框架而重复造轮子的,这个时候需要列出新框架主

如何写一个框架

定位 所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等. 如果你打算写框架,那么肯定心里已经有一个初步的定位,比如它是一个缓存框架.Web MVC框架.IOC框架.ORM/数据访问框架.RPC框架或是一个用于Web开发的全栈式框架. 是否要重复造轮子?除非是练手项目,一般我们是有了解决不了问题的时候才会考虑不使用既有的成熟的框架而重复造轮子的,这个时候需要列出新框架主要

Python的 GUI 框架

Python的 GUI 框架 Tkinter Python内嵌的gui环境,使用TCL实现,python IDLE由Tkinter实现 历史悠久,perl中有对应的perlTk.Python标准安装包中包含Tkinter,易学易用,方便创建简单GUI. 跨平台 布局全靠代码实现,15种常用部件,效果简陋 Wxpython 跨平台,由C++编写 Python的扩展模块,使用前需要安装 遵循LGPL协议,自由软件,商用许可 文档少,遇到问题不容易解决 代码布局控件,不直观 Pygtk Python对

(二)springMvc原理和手写springMvc框架

我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)SpringMVC流程 1.  用户发送请求至前端控制器DispatcherServlet. 2.  DispatcherServlet收到请求调用HandlerMapping处理器映射器. 3.  处理器映射器找到具体的处理器(可以根据xml配置.注解进行查找),生成处理器对象及处理器拦截器(如果有则

手写SpringMVC 框架

手写SpringMVC框架 细嗅蔷薇 心有猛虎 背景:Spring 想必大家都听说过,可能现在更多流行的是Spring Boot 和Spring Cloud 框架:但是SpringMVC 作为一款实现了MVC 设计模式的web (表现层) 层框架,其高开发效率和高性能也是现在很多公司仍在采用的框架:除此之外,Spring 源码大师级的代码规范和设计思想都十分值得学习:退一步说,Spring Boot 框架底层也有很多Spring 的东西,而且面试的时候还会经常被问到SpringMVC 原理,一般