2007-03-15 12:44

最近在用SmartWin++,感觉还不错,有空会写点东西放上来。

其实我这是二进宫了。很久前不知发了哪根神经,也曾经研究了一下,对她的基于策略的设计理念很是赞叹了一番,加之里面大量运用了STL、Boost等比较现代的库,可学性还是挺高的。

但我最终没有坚持下来。一方面我的工作并不需要GUI,另一方面她缺乏界面设计器,不能WYSIWYG,而这是GUI编程的大忌,所以虎头蛇尾,最后不了了之。

如今前度刘郎今又来,我保证不会再寻花问柳,一定有始有终:)

首先到SmartWin的网站(http://smartwin.sourceforge.net/)去下载,注意VC6是不能用的,因为SmartWin++大量运用了模板,而VC6对模板的支持实在太糟糕了:(

还好我有VC7.1,下完后直接用SmartWin.sln解决方案进行编译即可,整个过程大概几分钟吧。
我在编译过程中还出现一个小插曲,编译器莫名其妙报了个错,说跟某个Boost库链接错误。犹豫了1/4柱香后我明白了,原来我的lib路径设置中包含了以前编译过的Boost的库文件路径,所以在编译SmartWin++时错误的找到了以前的lib文件,把该路径删除后再进行编译,一切OK!

前面说过SmartWin缺乏WYSIWYG能力,其实也不是很全面。对于对话框,她还是可以利用VC的资源编辑器来进行创建的。

现在也有一个开源项目Sally(http://sallyide.sourceforge.net),提供对SmartWin的界面设计支持,我下载试了下,不是很好用,是用C#编写的,速度很慢(我的内存512M)。

不管如何,希望这次能真正搞清楚SmartWin++的设计机理,体会基于策略编程的妙处,这是我真正的目的。

2007-03-16 16:49

按照惯例,我们先来看看一个SmartWin++的小程序。这个程序很简单,就是显示一个窗口,然后在窗口中间输出一句话。。。你真聪明,那句话就是“Hello,world!”

完整代码如下:

//The first demo. Simple is beautiful!

#include "SmartWin.h"
using namespace SmartWin;

class Test : public WidgetFactory< WidgetWindow, Test >
{
public:
      Test()
      {}

void painting(SmartWin::Canvas &c)
      {
          SmartWin::Rectangle rc(getClientAreaSize());
          c.drawText(_T("Hello,World!"), rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
      }

void isResized(const SmartWin::WidgetSizedEventResult & sz)
      {
          updateWidget();
      }

void initAndCreate()
      {
          this->createWindow();
          this->setText(_T("Hello, world!"));
          this->setBounds(100,100,300,300);
          this->onPainting(&Test::painting);
          this->onSized(&Test::isResized);
      }

};

int SmartWinMain( Application & app)
{
      Test *test = new Test;
      test->initAndCreate();
      return app.run();
}

是不是很干净清爽?在你心动过后,我们赶紧来行动吧。你如果接触过GUI编程,那么你可能马上就会提出几个问题:

1。真正的程序入口函数WinMain躲在哪里?
2。Widget是如何产生,如何销毁的?
3。消息的处理机制是怎样的?
。。。。。。

别急,我也在探究这些问题,让我们一起来学习吧。(PS:我现在思路也比较乱,想到哪写到哪吧:))

我们看到有一个SmartWinMain,带有一个参数app,类型为Application。很好理解,这应该代表整个Instance,因此该app对象应该是全局唯一的(假如你头脑中马上蹦出“单件模式”,恭喜你!)。

其实SmartWinMain()并不是程序的入口,而是我们客户代码的入口。在它之前会有一些全局对象的创建初始化等工作。后面会有分析。

在这个SmartWinMain中创建了一个Test对象,Test类的古怪声明先不去管它,只要知道该Test对象对应着我们将展示的Windows窗口就行了,然后调用Test对象的initAndCreate()成员函数,可以看到在这个函数里面真正创建了一个Windows窗口,然后就是设置窗口标题,大小等等。

接着就是消息注册了。用过MFC的都知道,MFC里面有消息注册表,那些古怪的宏!在SmartWin里面没有这些东西,但为了实现消息的派发,也一定有类似的机制。大家看到在窗口初始化时需要自己注册感兴趣的消息。在这个简短的例子中我们只对两个消息感兴趣,就是WM_PAINT和WM_SIZE消息,因此需要写它们的消息处理代码,并且进行注册。小小的注意一下,当我们拖动窗口边框时,Windows会触发WM_SIZE消息,我们必须处理这个消息,这样才能让“Hello,world!”始终保持在窗口客户区中央。

Windows会在恰当的时刻将这两个消息送到SmartWin的消息处理系统中,最终会调用我们撰写的处理函数。其余的Windows消息我们并不关心,因此留给Windows缺省处理就好。这部分主题暂且不表,留待后续进行深究。

最后我们看到有一句app.run(),这里面就是消息循环了。当我们关闭窗口时,该函数就会返回,然后程序会执行一些清理资源的工作,保证(内存)资源不会泄漏。

千言万语,不知如何下笔 ;)

SmartWin++是一个复杂和精巧的框架,所谓复杂一方面是由于框架设计之固有权衡,例如对象的生灭过程,对象的继承图谱以及消息派发机制等等,另一方面也由于大量运用了模板,看看那些密密麻麻尖尖的括号,简直令人一个头两个大。还好我以前曾经对模板浸淫过一段时间,所以看起来还勉强可以理解。

所谓精巧当然是她的设计理念,也就是基于策略的设计理念。大名鼎鼎的《设计模式》里面就有策略模式,到了C++天才,Loki库作者Andrei Alexandrescu手里,策略模式被升华为基于策略的设计,结合C++的语言特点(主要是模板),他阐述了一整套的设计思维,把事物拆分成一个个小粒度的正交的策略对象,然后运用模板把这些策略在编译期装配起来,只需要几行代码就可以组装产生带不同策略的事物。这里就不展开了,有兴趣可参考Andrei的书《Modern C++ Design》;)

我以前也写过几篇关于基于策略设计的小文章,当然是十分的粗浅,比起SmartWin++里面的运用来,可说是小巫见大巫了。有兴趣者可参考。

在深度钻研SmartWin++框架的同时,我对Policy-Based Class Design也有进一步的体会,以后我也会写出来跟大家分享。

【附】以前写的两篇小文:
1。基于策略的设计(1)http://hi.baidu.com/blue%5Fnever%5Fdied/blog/item/631feac4823942a98226ace7.html

2。基于策略的设计(2)http://hi.baidu.com/blue%5Fnever%5Fdied/blog/item/09ffd62ae825242dd42af1e7.html

2007-03-20 17:52

假设你有SDK的开发经验,那么你一定不会忘记怎样写一个最简单的Windows程序。不外乎先填写一个窗口类,设定窗口的图标,大小,窗口处理函数指针等等,然后进行注册。接着你必须提供一个窗口函数,对你感兴趣的消息进行处理。最后你还必须自己写一个消息泵,不断的从消息队列中获取消息进行派发。整个过程大致就是这样。

现在我们改用C++,改用SmartWin框架来进行开发,但是也一定可以预期整个的底层运行过程,还是像SDK程序一样。正所谓万变不离其宗,换汤不换药是也。

下面我们就来探究一下。经过一番苦苦追寻。。。(此处省略500字,我容易吗我?当然你看这篇可能狗屁不通的文章更不容易;)),我们大致可以得到一些战利品。

首先我们必须知道,所有C++ GUI框架都回避不了一个问题,那就是this指针的问题。窗口函数实际上是一个回调函数,意味着当窗口收到消息时,Windows系统会善解人意的替我们调用该函数,所以我们必须事先把该函数的地址注册到Windows系统中。

现在我们用C++类来进行封装,此窗口函数就变成C++的“成员函数”,而成员函数的调用比较复杂,必须先知道对象的地址,类似这样变态的写法:(p->*f)() //p是对象地址,f是成员函数指针。而且我们也知道,C++类里面的所有成员函数,善解人意的C++编译器都会在参数列表里面加上this指针,这样也违反了Windows系统要求的窗口函数声明。

我们别无选择,只能迁就Windows,让Windows像以前那样来调用窗口函数,这意味着窗口函数看起来必须像个普通的C函数。你睿智的大脑转了半下,马上就领会到:可以把窗口函数声明成static!这样不就没有this指针了吗?

且慢!这样当然可以,但没有this,你怎样去访问类里面的其他数据和方法呢?

怎么办?凉拌。如果我们能在static窗口函数中,偷天换日取到窗口类的this指针,则此问题可解。有许多方法,大家各显神通。像ATL/WTL里面,就是运用所谓的Thunk技术,通过汇编来解决问题。这里我来介绍下SmartWin的做法。

还是来看看代码吧。我们创建一个窗口,必须调用createWindow()方法,该方法最终会调用Widget类的create()方法,Seed相当于窗口的风格(大小,图标,背景色。。。),Widget类是所有窗口的基类。

template< class EventHandlerClass, class MessageMapPolicy >
void WidgetWindow< EventHandlerClass, MessageMapPolicy >::createWindow( Seed cs )
{
Application::instance().generateLocalClassName( cs );
itsRegisteredClassName = cs.getClassName();

SMARTWIN_WNDCLASSEX ws;

#ifndef WINCE
ws.cbSize = sizeof( SMARTWIN_WNDCLASSEX );
#endif //! WINCE
// This are window class styles, not window styles ...
ws.style = CS_DBLCLKS; // Allow double click messages
ws.lpfnWndProc = MessageMapPolicy::mainWndProc_;
ws.cbClsExtra = 0;
ws.cbWndExtra = 0;
ws.hInstance = Application::instance().getAppHandle();
#ifdef WINCE
ws.hIcon = 0;
#else
ws.hIcon = cs.icon;
#endif //! WINCE
ws.hCursor = cs.cursor;
ws.hbrBackground = cs.background;
ws.lpszMenuName = cs.menuName.empty() ? 0 : cs.menuName.c_str();
ws.lpszClassName = itsRegisteredClassName.c_str();
#ifndef WINCE
//TODO: fix this
ws.hIconSm = cs.icon;
#endif //! WINCE

ATOM registeredClass = SmartWinRegisterClass( & ws );
if ( 0 == registeredClass )
{
     xCeption x( _T( "WidgetWindowBase.createWindow() SmartWinRegisterClass fizzled..." ) );
     throw x;
}
Application::instance().addLocalWindowClassToUnregister( cs );
Widget::create( cs );
}

注意到ws.lpfnWndProc = MessageMapPolicy::mainWndProc_这行,这是设置窗口函数为mainWndProc_,而mainWndProc_恰恰是一个static成员函数。MessageMapPolicy什么的先不管,后面会介绍。大家看到这其实也就是一个简单的封装,先设置SMARTWIN_WNDCLASSEX结构,然后调用SmartWinRegisterClass()进行注册窗口风格类。

#define SMARTWIN_WNDCLASSEX WNDCLASSEX
#define SmartWinRegisterClass RegisterClassEx

最后真正创建窗口是调用Widget::create()方法。

void Widget::create( const SmartWin::Seed & cs )
{
itsHandle = ::CreateWindowEx( cs.exStyle,
     cs.getClassName().c_str(),
     cs.caption.c_str(),
     cs.style,
     cs.location.pos.x, cs.location.pos.y, cs.location.size.x, cs.location.size.y,
     itsParent ? itsParent->handle() : 0,
     cs.menuHandle == - 1
      ? reinterpret_cast< HMENU >( getCtrlId() )  
      : reinterpret_cast< HMENU >( cs.menuHandle ),
     Application::instance().getAppHandle(),
     reinterpret_cast< LPVOID >( this ) );
if ( !itsHandle )
{
     // The most common error is to forget WS_CHILD in the styles
     xCeption x( _T( "CreateWindowEx in Widget::create fizzled ..." ) );
     throw x;
}
isChild = ( ( cs.style & WS_CHILD ) == WS_CHILD );
Application::instance().registerWidget( this );
}

我们看到create()方法还是调用了SDK函数CreateWindowEx(),注意最后一个参数:reinterpret_cast< LPVOID >( this )。CreateWindowEx()可以向窗口传递一些附加信息,最后这个参数就代表这些附加信息的地址。这里我们先把this指针转换成void,然后当作附加信息传递给窗口。

然后当我们的窗口处理函数mainWndProc_()开始运转时,我们来看看是怎样变魔术的。通常窗口收到的第一个消息是WM_NCCREATE,该消息的参数lParam是我们特别关心的,因为lParam就是CREATESTRUCT指针。

static LRESULT CALLBACK mainWndProc_( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
       Widget * This = reinterpret_cast< Widget * >( ::GetProp( hWnd, _T( "_mainWndProc" ) ) );
       if ( !This )
       {
           if ( uMsg == WM_NCCREATE )
           {
               CREATESTRUCT * cs = reinterpret_cast< CREATESTRUCT * >( lParam );
               This = reinterpret_cast< Widget * >( cs->lpCreateParams );
               ::SetProp( hWnd, _T( "_mainWndProc" ), reinterpret_cast< HANDLE >( This ) );
               private_::setHandle( This, hWnd );
           }
           else if ( uMsg != WM_CREATE )
               return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
       }
       return This->sendWidgetMessage( hWnd, uMsg, wParam, lParam );
}

收到WM_NCCREATE消息后,把lParam转换成CREATESTRUCT指针,CREATESTRUCT结构的lpCreateParams指针,就是指向我们先前传递给窗口的附加信息,还记得吗?这样我们就把先前寄存的this指针取回来。然后呢,我们又把this转换成HANDLE,调用SetProp()将它再次存回到窗口内存中。

看官要问,何必多此一举?那我先反问一下,得到这个this指针后,要存在哪呢?当成全局指针?不安全,而且假如要创建多个同类型窗口呢?

请注意,每个窗口对应一个C++对象,所以如果能把这些C++对象的地址存放在每个窗口对应的内存中,那简直就是完美的令人发指。而SetProp()和GetProp()正有此功用。它们可以通过一个key值(就是自定义的"_mainWndProc")来进行存取。

现在你再回头看看mainWndProc_()窗口处理函数,是不是豁然开朗了?窗口收到消息后,mainWndProc_()函数会被调用,它首先调用GetProp()看能不能得到this指针,假如得不到,再看看窗口是不是已经初始化(收到WM_NCCREATE),如果是WM_NCCREATE,就从lParam中得到先前寄存的this指针,然后赶紧把this指针存起来。下次有新消息进来,则可以直接得到this,然后调用This->sendWidgetMessage()方法把消息派发进去,我们也可以为所欲为啦。

唉,说清楚真累,喝口水先。

C++真是太有才鸟。

有许多计算机语言,一出生就决定了她的基本功能和基本用法,所谓的一切尽在掌握。虽然可能同样很强大,但却缺少惊奇。

但C++不。C++就象一个桀骜不逊的男人,特立独行,极具创造力、表现力。这也是我热爱这门语言的最大原因。比如说模板,当初C++标准委员会把模板功能加进来,绝对想不到会给C++带来如此大的变化,甚至创造了一种新的设计思想--泛型设计。

好了,我们暂且收回来一下,还是回到咱们研究的SmartWin++框架上。前面也提到,作者看了《Modern C++ Design》后,十分推崇基于策略设计思想,也成功的在SmartWin++框架实践了一把。

那么我们就来看看,基于策略的设计到底是什么回事,有哪些表现方式?为了给大家一个基本印象,我先来超浓缩一下。

首先我们有一个宿主类Host,这就是我们想干的事情,然后这个事情我们想办法把她分成几个策略,比如Policy1、Policy2。。。

我们先来看看最简单的情况,假设这些策略都是具体类(非模板类)。那么我们可能这么写:
class Host : public Policy1, public Polciy2 {...};

基于策略的思想和多继承结合,能够产生十分具表现力的设计。现在我们看到Host类就具备Policy1和Policy2的能力,或者换句话,Host把Policy1和Policy2装配起来。

这是最简单的情况,这种设计有个缺陷,就是Policy和Host之间的关系是单向的,Host可以访问Policy的方法,反过来却不行。

假如我们需要一个双向的关系,那么可以这样写:
template < class Container > class Policy1 {...};
template < class Container > class Policy2 {...};
class Host : public Policy1< Host >, public Policy2< Host > {...};

我们把Policy变成了模板类,模板参数就是Host。这有什么好处呢?呵呵,这样做就让Policy知道了Host的类型!知道了Host类型后,我们就可以在Policy里面把Policy的this指针动态转换成Host的this指针!!

看起来似乎有一点难以理解,嗯,首先我们别忘了Policy一般不会单独存在的,她肯定要嵌入到Host中去,她们是一体的。这句话的意思就是从内存布局看,Policy对象的this指针就是Host对象的this指针。我们都知道基类对象和派生类对象的指针可以互相转换,就是这个道理。

当然在多继承的情况下有一点复杂,比如Policy1和Host的地址一样,Policy2的地址则有些偏移。不过当我们调用cast的时候,编译器会善解人意的进行调整,所以我们无需担心这个问题。

我们前面都假设Policy之间是互相正交的,实际上我们做设计时也应该尽量保证这一点。但有时候我们无可避免Policy之间也存在耦合,那又该怎么办?

别急,我们可以这样写,先吸口气:)
template < class Container > class Policy1 {...};
template < class Container, class OtherPolicy > class Policy2 {...};
class Host : public Policy1< Host >, public Policy2< Host, Policy1< Host> > {...};

我们可以看到,现在Policy1变成了Policy2的模板实例化参数。当Host对象实例化的时候,编译器首先对Policy1实例化,然后就可以对Policy2实例化了,记住Host在这些Policy中只充当占位苻的作用。等到所有Policy都实例化完毕后,就可以回过头来实例化Host了。

也许还有其他的搭配方式,但我个人认为基本上就这些了。掌握了基本的原理,看起代码就会比较轻松。

好吧,我们不要光说不练,下面就来看一个例子:

#include <iostream>
#include "boost/cast.hpp"

template < class Container >
class Policy1
{
public:
      void Hello1()
      {
          Container *ph = boost::polymorphic_cast< Container* >(this);
          std::cout << "Policy1::Hello1() --> ";
          ph->DoHello();
      }

void Magic1()
      {
          std::cout << "Policy1::Magic()" << std::endl;
      }

virtual ~Policy1() {}
};

template < class Container, class OtherPolicy >
class Policy2
{
public:
      void Hello2()
      {
          Container *ph = boost::polymorphic_cast< Container* >(this);
          std::cout << "Ploicy2::Hello2() --> ";
          ph->DoHello();
      }

void Magic2()
      {
          OtherPolicy *po = boost::polymorphic_cast< OtherPolicy* >(this);
          std::cout << "Ploicy2::Magic2() --> ";
          po->Magic1();
      }

virtual ~Policy2() {}
};
class Host : public Policy1<Host>, public Policy2<Host, Policy1< Host > >
{
public:
      void DoHello()
      {
          std::cout << "Host::DoHello()" << std::endl;
      }
};

int main()
{
      Host *ph = new Host();
      ph->Hello1();
      ph->Hello2();
      ph->Magic2();

return 0;
}

运行结果如下:
Policy1::Hello1() --> Host::DoHello()
Ploicy2::Hello2() --> Host::DoHello()
Ploicy2::Magic2() --> Policy1::Magic()

仔细体会一下吧,呵呵。

2007-03-26 17:18

越深入研究SmartWin++框架,越了解作者的想法,就越有一种在深山里寻找宝藏的感觉,峰回路转,柳暗花明,终点就在前面若隐若现。。。

呵呵,先简单抒了下情,下面继续我们的寻宝之旅。今天来谈谈SmartWin++的事件处理机制。

假如你写过SDK程序,那么你可能有印象,你必须有一个主窗口,主窗口必须有一个窗口函数来处理各种消息;你的主程序还必须有一个消息泵,从消息队列里面获取消息,然后进行预处理和指配。

稍微复杂一点的程序,一般还有多个子窗口和控件,如果需要的话,你可以对这些子窗口和控件进行“子类化”,这相当于提供了一个钩子,让你可以拦截某些感兴趣的Windows消息进行特殊的处理,当然最后别忘了调用该子窗口缺省的处理函数,她们会尽心尽职地处理那些你不感兴趣的消息。

当我们对窗口进行操作时,Windows系统产生了很多消息,这些消息首先被进程的消息泵进行处理,消息泵根据消息的目的地(不同的窗口句柄hwnd)派发到不同的窗口处理函数中。一般来讲,子窗口会视情况将某些消息重新打包发给父窗口消息处理函数,比如你单击了某个按钮,该按钮缺省消息处理函数就会向主窗口发送一个WM_COMMAND的消息。

在SmartWin++中,有一个全局的Application类,就充当了这个消息泵的功能。当然这个消息泵功能更强大,她调用了API函数MsgWaitForMultipleObjectsEx(),除了可以派发一般消息外,还可以派发各种内核事件Event,我理解这样做的好处是提供了一个统一的界面来处理各种信号(消息和事件)。

要讲清楚SmartWin++的消息处理机制,让我十分踌躇。因为这不可避免会涉及到具体的类设计和职责划分,而我又担心会陷入代码的泥潭。我觉得从总体上来把握框架的设计思想可能会更重要。

我想换个角度来思考,假如让你来设计这么个框架,你会怎么做?当然你可能会谦虚的抿一下嘴,沉默的注视着我。OK,我想有两个方面是必须考虑的:(1)Widget的产生、销毁以及管理;(2)消息的派发、路由机制。

首先需要明确,所有Widgets都在Heap而不是在堆栈中产生。Application对象维护一张全局表,任何新创建的窗口、子窗口和控件都会把地址自动注册到这种表中。所有窗口和控件都从类Widget派生,而创建Widget会调用Widget::create()方法,在该方法中会自动进行注册。在Widget的析构函数中,会自动在Application的全局表中删除Widget对应的地址。

每一个窗口也会维护一张子窗口(控件)的表,当子Widget实例化时(Widget的构造函数中),子Widget会将自己的地址注册到父Widget维护的表中。当然也有例外,对于菜单来说,就不需要这么做。

SmartWin++框架自动定义了对WM_NCDESTROY消息的处理,在这里面会调用MessageMapPolicy::kill();把Widget和子Widget们销毁掉。因此,我们无需担心内存泄漏的问题,SmartWin++已经帮我们管理好了。

在前面的SmartWin++例子中,我曾经说过,每个程序都有不同的策略,其中一个策略就是关于消息映射和处理的,SmartWin++框架提供了MessageMapPolicyDialogWidget、MessageMapPolicyModalDialogWidget、MessageMapPolicyNormalWidget和MessageMapPolicyMDIChildWidget等策略,顾名思义可以看出分别对应不同的窗口形态。

这些策略就是定义了如何处理消息的,她们定义了一个统一的static接口:
static LRESULT CALLBACK mainWndProc_( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
很显然这就是每个窗口的消息处理函数!

另外在罗嗦一下,不光父Widget的窗口函数是这个,子Widget也一样!子Widget(比如说一个按钮)在创建时,会调用ThisMessageMap::createMessageMap();这会进行相应的子类化,同时把子Widget的窗口过程设置为mainWndProc_()。

接下来我们可能会问,我们需要处理很多消息,这些消息消息处理函数要放在哪呢?这牵扯到SmartWin++的另外一个重要的类MessageMapBase。这个类也有一张表,我们注册消息处理函数时就放到这里面。

每一个窗口(普通窗口、子窗口和控件)都从很多类进行多继承,其中就包括上面讲的Widget、MessageMapPolicy(几种Policy之一)、MessageMapBase以及其他一些策略类。

好了,有了这些背景介绍后,我们可以对SmartWin++的消息机制来进行一个纵览。

为了更具体的描述,我们假设有一个窗口,窗口的客户区有一个按钮。我们来增加两个消息处理函数,一个是主窗口的WM_SIZE消息,一个是点击按钮后产生的WM_COMMAND消息。

首先我们来看看WM_SIZE消息的处理。全局的Application对象会获取到该消息,然后会把她派发到主窗口的窗口过程(根据窗口句柄hwnd),也就是主窗口的mainWndProc_()函数中。在该静态函数中会取到自身的this指针,然后调用sendWidgetMessage()函数进一步处理。在该函数中首先会先看看消息是不是自己能处理(通过在主窗口的消息注册表中进行查找),因为我们定义了WM_SIZE的处理函数,所以会找到并调用该处理函数,消息处理完毕。

接着看看WM_COMMAND消息的处理。当用户单击按钮时,Windows会产生相应的消息(我不是很确定是什么消息,反正不是WM_COMMAND,先假设这条消息叫WM_A吧)。同样Application对象会接收到WM_A消息,并把他派发到按钮的窗口过程中,注意按钮的窗口过程也是mainWndProc_()。显然在该窗口过程没有处理WM_A消息,因此该消息被转往按钮的缺省窗口过程去处理,在那里面WM_A会变成WM_COMMAND消息,并被发往父窗口。

好了,现在WM_COMMAND消息同样被Application对象获取到了。和前面的WM_SIZE一样,主窗口过程先看看自己能不能处理。假如主窗口没有定义处理的话,则看看是什么消息,如果是那些需要“反射”的消息,比如WM_COMMAND、WM_NOTIFY、WM_DRAWITEM等,则调用mainWndRouteChild()函数,把消息转给子窗口去处理。在mainWndRouteChild()中根据消息所携带的目的地地址(目的窗口句柄),看是哪个子窗口,然后调用该子窗口的sendWidgetMessage()方法去进一步处理。在子窗口的sendWidgetMessage()方法中,同样的会去查找子窗口的消息注册表,看是否定义了该消息的处理函数,如果没有,则转入该子窗口的缺省处理函数中去。

说得口干舌燥,都不知说清楚了没有:)

假如你用过MFC,那么你可能知道MFC有一张全局的消息映射表(那些古怪的宏:)),但是在SmartWin++中,每个窗口Widget都有一张映射表,这是不同的地方。

续我们的SmartWin++寻宝之旅。上篇文章从总体上介绍了消息处理机制,这篇继续对一些细节进行研究。

还是用我们的老方法,假如让你来用SmartWin++框架,让你来写一些事件处理的代码,你觉得怎样写比较爽?

你可能想都不想就会说:注册感兴趣的事件,撰写事件处理代码。OVER。

没错,大致上是这样,但回答还是太粗糙了。以我自己的体会,一是不希望写消息注册代码,而是直接写消息处理代码即可;二是消息处理代码应该有一个非常友好的调用接口(或者说签名)。

第一点你可能觉得奇怪,不注册怎么让框架来回调呢?呵呵,有些GUI框架就做到了,比如win32gui,消息处理函数中就隐含着消息注册,他用了一些magic般的手法来做到这一点,所以不需要显式的去注册消息。这在语法上带来一些甜头,代价就是编译时间的开销。当然目前SmartWin++不支持这一点,还是需要你显式去进行消息注册。

第二点就很好理解了。我们当然不希望面对着一个“裸”的消息处理接口。还记得SDK吗?所有消息处理都是从
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
开始,你自己得去转换那些令人头疼的wParam和lParam,这会让人觉得仿佛回到了原始社会:)所以我们理所当然期望框架先帮我们做好预处理,不需要我们直接去面对这些wParam和lParam。

如果站在框架设计者的角度,那要考虑的东西就多了。首先是一个消息注册的问题,前面我们轻飘飘的一句话带过,仿佛很简单似的,其实没那么容易。我们会有消息注册表,但显然这些表项应该有统一的方式。

假设我们的消息处理函数都是类成员函数(member function),例如前面的this->onPainting(&Test::painting);和this->onSized(&Test::isResized);等等。我们都知道成员函数的调用语法是比较变态的,你必须知道对象的地址,成员函数的地址,成员函数的入参等等,然后才能进行调用。

举个例子吧。我们在前面例子中有个窗口Test,我们想处理WM_PAINT和WM_SIZE消息,确保在窗口中央显示“Hello,World”,看起来程序大致是这样:
#include "SmartWin.h"
using namespace SmartWin;

class Test : public WidgetFactory< WidgetWindow, Test >
{
public:
      Test()
      {}

void painting(SmartWin::Canvas &c)
      {
          SmartWin::Rectangle rc(getClientAreaSize());
          c.drawText(_T("Hello,World!"), rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
      }

void isResized(const SmartWin::WidgetSizedEventResult & sz)
      {
          updateWidget();
      }

void initAndCreate()
      {
          this->createWindow();
          this->onPainting(&Test::painting); //注册WM_PAINT消息处理函数
          this->onSized(&Test::isResized);     //注册WM_SIZE消息处理函数
      }
};

int SmartWinMain( Application & app)
{
      Test *test = new Test;
      test->initAndCreate();
      return app.run();
}

为了让SmartWin++来回调我们注册的消息处理函数,SmartWin++框架必须知道几样东西:test对象的地址,painting(...)和isResized(...)成员函数的地址,而且可以看到,painting(...)和isResized(...)的签名不一样,这意味着SmartWin++在回调时必须想办法产生她们各自需要的入参。

看起来似乎没办法进行统一。别急,我们先把消息处理函数的入参去掉,painting(...)变成painting(),isResized(...)变成isResized(),这样做是因为入参可以通过wParam和lParam进行转换得来,我们可以在框架的某个地方来构造这些参数(假如你能脱口说出“这不就是各个策略嘛”,那你就太厉害了)。

我们是怎样做到这一点?答案就是“暴力转换法”。比如SmartWin++框架定义了一个成员函数指针类型:
typedef void ( Widget::* itsVoidFunction )( void );
我们就可以这样来暴力转换:
reinterpret_cast< itsVoidFunction >( eventHandler )
eventHandler是这样的类型:
typedef void ( EventHandlerClass::* itsVoidFunctionTakingCanvas )( Canvas & );
这是在MessageMap类中声明的。可以看到,eventHandler就是我们在Test中定义的消息处理函数的类型!

现在我们就将这些消息处理函数变成统一的形式了,无疑大大降低了难度。但这样还是不能直接去进行注册,因为前面也说过,成员函数的回调需要很多信息。

软件领域有句名言:当你解决不了一个问题时,给它增加一个额外的中间层。说这句话的人简直太有才了。

SmartWin++也响应了这个号召,她给这些成员函数再增加了一层封装,我们先来看下代码:
template< class EventHandlerClass, class WidgetType, class MessageMapType >
class AspectPaintingDispatcher<EventHandlerClass, WidgetType, MessageMapType, false/*Container Widget*/>
{
public:
static HRESULT dispatchPaintThis( private_::SignalContent & params )
{
    typename MessageMapType::itsVoidFunctionTakingCanvas func =
     reinterpret_cast< typename MessageMapType::itsVoidFunctionTakingCanvas >( params.FunctionThis );

SmartWin::PaintCanvas canvas( params.This->handle() );

EventHandlerClass * ThisParent = internal_::getTypedParentOrThrow < EventHandlerClass * >( params.This );

( ( * ThisParent ).*func )(
     canvas
     );

return ThisParent->returnFromHandledWindowProc( reinterpret_cast< HWND >( params.Msg.Handle ), params.Msg.Msg, params.Msg.WParam, params.Msg.LParam );
}
//...
}

template< class EventHandlerClass, class WidgetType, class MessageMapType >
void AspectPainting< EventHandlerClass, WidgetType, MessageMapType >::onPainting( typename MessageMapType::itsVoidFunctionTakingCanvas eventHandler )
{
MessageMapType * ptrThis = boost::polymorphic_cast< MessageMapType * >( this );
ptrThis->addNewSignal(
    typename MessageMapType::SignalTupleType(
     private_::SignalContent(
      Message( WM_PAINT ),
      reinterpret_cast< itsVoidFunction >( eventHandler ),
      ptrThis
     ),
     typename MessageMapType::SignalType(
      typename MessageMapType::SignalType::SlotType( & Dispatcher::dispatchPaintThis )
     )
    )
);
}

如果你感到有一点眩晕,那很正常,呵呵。消息注册表其实是一个std::vector,表项类型是一个boost::tuple:

boost::tuple < private_::SignalContent, SigSlot::Signal< HRESULT, private_::SignalContent > > >

这个boost::tuple由两部分组成,一个是SignalContent,一个是Signal。SignalContent是一个POD类,是消息的上下文,里面包含我们需要的东西:窗口对象的地址,被暴力转换后的成员函数地址,消息ID等等。Signal是一个模板类,可以看成一个functon object,里面保存的是一个boost::function对象,这个function对象对应着一个static函数:
static HRESULT dispatchPaintThis( private_::SignalContent & params )
请记住所有消息处理函数都被转换成这样的统一形式。这样的形式当然是有目的的,首先多了个返回值HRESULT,这样框架可以知道消息是否已经被正确处理,然后框架可以决定是否对消息进行路由;其次这个函数是属于各个Policy类的(在SmartWin++里面叫Aspect),里面可以产生消息处理函数需要的入参,比如painting(...)函数需要的Canvas。

我们看看dispatchPaintThis(...)最后还是调用了:
( ( * ThisParent ).*func )(canvas);
这不就是我们梦寐以求的结果吗?哈哈哈!

我们知道一个窗口,一个控件,会有许多属性或者行为。比如窗口可以画画,可以拖动,可以响应鼠标点击。。。按照传统的OO思想,我们很可能把这些属性、行为糅合在一个大的类中,这种类有个雅称:上帝类。

没错,无所不能的上帝。但在我们的软件设计领域,这可不是什么好事情,因为这往往意味着一样事情:耦合,而这正是软件工程师的梦魇。

所以假如我们能把这些属性行为分门别类,让她们之间尽量独立,那么可维护性、可扩展性无疑会大大改善。SmartWin++采取基于策略的设计理念,这使得她在那么多的C++框架中独树一帜,真的很迷人。

我们以一个按钮类来展示一下:
template< class EventHandlerClass, class MessageMapPolicy >
class WidgetButton :
public MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy >,
public virtual TrueWindow,

// Aspects
public AspectBackgroundColor< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectBorder< WidgetButton< EventHandlerClass, MessageMapPolicy > >,
public AspectClickable< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectDblClickable< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectEnabled< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectFocus< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectFont< WidgetButton< EventHandlerClass, MessageMapPolicy > >,
public AspectKeyPressed< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectPainting< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectRaw< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectSizable< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectText< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectThreads< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >,
public AspectVisible< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapControl< EventHandlerClass, WidgetButton< EventHandlerClass, MessageMapPolicy >, MessageMapPolicy > >
{...}

站直了,别趴下,兄弟!尽管C++模板的语法可能让你眩晕,但没关系,一旦你体会了模板带来的设计维度上的提升,你一定会喜欢上她的。相信我,你能做到的。

好了,我们注意下以Aspect...开头的那些模板类,这其实就是我们前面说的Policy类,在SmartWin++框架里面作者把她们叫做Aspect(方面),有点AOP的味道,其实是一回事。这些Aspect定义了按钮的很多行为,比如设置字体,响应鼠标点击,设置使能等等,我们利用多继承把这些Aspect糅合起来,就变成了一个完整的按钮行为。

假如我们需要新的行为,那么很简单,再设计一个新的Aspect,然后让按钮类继承之,就这么简单!

还有个好处,就是每个控件需要的Aspect是不一样的,那么每个控件都可以根据自己需要去挑选适当的Aspect,这简直就像是组装产品一样。

为了达到这个目的,Aspect的划分就显得非常重要,Aspect的设计也必须具有足够的抽象性和正交性。一个Aspect的主要职责是什么呢?一是负责注册Aspect对应的相关消息,二是进行消息的预处理,也就是在调用用户提供的消息处理函数前,为这个消息处理函数提供适当的context,比如假如是响应WM_PAINT消息,那么AspectPainting就会自己产生一个Canvas,让客户代码可以在上面绘画。

在前面的文章中,我们介绍了消息处理机制的一些细节,我们说过SmartWin++会将客户提供的消息处理函数进行封装,变成统一的形式后(用boost::tuple来存储信息)再进行注册,注册到一个std::vector中。当SmartWin++框架接收到消息,需要回调客户提供的消息处理函数时,我们来看看是怎样处理的(还是以WM_PAINT为例):

template< class EventHandlerClass, class WidgetType, class MessageMapType >
class AspectPaintingDispatcher<EventHandlerClass, WidgetType, MessageMapType, false/*Container Widget*/>
{
public:
static HRESULT dispatchPaintThis( private_::SignalContent & params )
{
    typename MessageMapType::itsVoidFunctionTakingCanvas func =
     reinterpret_cast< typename MessageMapType::itsVoidFunctionTakingCanvas >( params.FunctionThis );

SmartWin::PaintCanvas canvas( params.This->handle() );

EventHandlerClass * ThisParent = internal_::getTypedParentOrThrow < EventHandlerClass * >( params.This );

( ( * ThisParent ).*func )(
     canvas
     );

return ThisParent->returnFromHandledWindowProc( reinterpret_cast< HWND >( params.Msg.Handle ), params.Msg.Msg, params.Msg.WParam, params.Msg.LParam );
}
//...
}

我们可以看到SmartWin++框架会调用一个dispatchPaintThis()的static函数,这个函数是SmartWin++变换成统一形式后注册的。这个函数里,我们得到了func(成员函数指针,同样经过了暴力换换),ThisParent(就是我们的主窗口Test的地址),还有我们需要的canvas,万事具备,接下来就会调用
( ( * ThisParent ).*func )(canvas);而这个就是我们提供的void painting(SmartWin::Canvas &c);成员函数。

这里面还有一个问题就是ThisParent的获取,SmartWin++用了一个模板函数getTypedParentOrThrow()来进行转换,我们来看看是何方神圣:

template< class Target, class Source >
inline Target getTypedParentOrThrow( Source * source )
{
// The easy shortcut
Target tmp = dynamic_cast< Target >( source );
if ( tmp )
    return tmp;

Widget * widget = dynamic_cast< Widget * >( source );
widget = widget->getParent();
while ( widget )
{
    Target tmp = dynamic_cast< Target >( widget );
    if ( tmp )
     return tmp;
    widget = widget->getParent();
}
throw std::bad_cast();
}

嗯,原来如此。首先是直接用dynamic_cast来进行动态转换,这可能成功,也可能失败。比如当前是一个按钮,那你就不能直接将她转换为一个窗口指针,我们需要的是按钮的父窗口指针,因为我们的按钮单击消息处理函数是定义在父窗口里面的。我们可以顺藤摸瓜,不断的获取widget->getParent(),然后再看看能不能转换成父窗口,直到最终成功为止。

2007-04-01 14:21

经过前段时间孜孜不倦的钻研,对SmartWin++架构方面的认识,算是略有小成。接下来的重点,可能会放在应用层面,探讨如何运用她来进行应用程序开发。

按作者想法,SmartWin++并不想成为大而全的巨无霸框架,而是想在GUI开发方面做到最好,提供给大家最简单易用的GUI开发工具。但这似乎又同作者的另一个想法相矛盾,那就是使SmartWin++成为MFC/WTL的替代者。

在GUI界面开发方面,我相信SmartWin++的优势,但MFC/WTL,尤其是MFC已经发展非常成熟,有着完备的功能,而这些功能都是实际开发中必须的。比如MFC所支持的序列化、Document-View架构、数据库访问、Internet类库。。。等等,这些都是SmartWin++目前所欠缺的功能。

当然作为一个只有短暂历史的GUI框架,这是可以理解的。我所担心的是另外一个事情,那就是SmartWin++社区的发展壮大。只有越来越多的程序员对她感兴趣,才有更多的精英分子为这个框架添砖加瓦,不断完善相关的功能。这样反过来也会促进社区的发展,最终走上良性循环之路。

目前SmartWin++社区最大的问题,就是社区太小了。用Google或者百度搜索,得到的有价值的页面寥寥无几;Sourceforge上的论坛,也不是很火爆。目前SmartWin++的主力开发者,也就是创始者一人,这导致了进度比较缓慢,远远不能满足需求。而且这样还有一个缺点,SmartWin++的功能需求没有一个很好的规划。往往是作者工作中需要什么功能,他就会把这些功能优先加入到SmartWin++中来,比如SOAP,呵呵。

我觉得现在SmartWin++基本功能已经完备了,正面临一个井喷的局面。假如能想办法多做做宣传,多出一些技术分析报告,吸引更多的优秀程序员来关注,那么就能慢慢壮大社区,促进SmartWin++的推广。

相信SmartWin++会有一个光明的未来!

SmartWin++笔记相关推荐

  1. 【读书笔记】知易行难,多实践

    前言: 其实,我不喜欢看书,只是喜欢找答案,想通过专业的解答来解决我生活的困惑.所以,我听了很多书,也看了很多书,但看完书,没有很多的实践,导致我并不很深入在很多时候. 分享读书笔记: <高效1 ...

  2. 【运维学习笔记】生命不息,搞事开始。。。

    001生命不息,搞事不止!!! 这段时间和hexesdesu搞了很多事情! 之前是机械硬盘和固态硬盘的测速,我就在那默默的看着他一个硬盘一个机械测来测去. 坐在他后面,每天都能看到这位萌萌的小男孩,各 ...

  3. SSAN 关系抽取 论文笔记

    20210621 https://zhuanlan.zhihu.com/p/353183322 [KG笔记]八.文档级(Document Level)关系抽取任务 共指id嵌入一样 但是实体嵌入的时候 ...

  4. pandas以前笔记

    # -*- coding: utf-8 -*- """ Created on Sat Jul 21 20:06:20 2018@author: heimi "& ...

  5. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  6. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  7. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  8. 王道考研 计算机网络笔记 第六章:应用层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

  9. 王道考研 计算机网络笔记 第五章:传输层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

最新文章

  1. K - Candies POJ - 3159(利用了自定义比较操作符)
  2. Codeforces 454C - Little Pony and Expected Maximum
  3. hibernate 无法保存timestamp_为什么很多人不愿意用hibernate了?
  4. 将Jquery序列化后的表单值转换成Json
  5. ubuntu安装完后需进行必要的软件更新
  6. redis内存行数据库细节
  7. 安装和使用VCLSkin美肤插件
  8. python编写代码实现文件的拷贝功能_如何使用Python脚本实现文件拷贝
  9. 图形学笔记(四)——Harris 角点检测器延申
  10. 《英雄联盟》“被手游”背后,是移动电竞的成长期“烦恼”
  11. 【CPRI】(3)帧格式详解(重点)
  12. ei会议和ei源刊的区别_ei和核心期刊的区别在哪里?
  13. 小郡肝火锅点餐系统代码实现(部分)
  14. 【已解决】格式化SD卡提示“这张磁盘有写保护”,试过将SD开关拨向两边,均失败。
  15. 【释义详解】Software License (软件许可证)是什么?
  16. ​​商朝是广西骆越人北上建立的政权,是骆越文化的延续
  17. 超微服务器性能,读取性能强劲 超微2U机架F228服务器评测
  18. TF卡/SD卡 异常问题,识别不了
  19. centos7 glibc2.17升级到glibc2.28
  20. 信息系统项目管理师(2022年)—— 重点内容:10大管理、5大过程组、47个过程信息汇总

热门文章

  1. 微信支付【 wx.chooseWXPay、WeixinJSBridge.invoke】
  2. 一键学会三种定位布局,相对定位、绝对定位、固定定位!
  3. 10分钟教你用 Python 控制键盘和鼠标
  4. 随机森林回归预测r语言_R包 randomForest 进行随机森林分析
  5. openstack-nova源码分析(十一)rebuild重建
  6. php和python学不明白_现在自学php和python那个合适?
  7. vba xla文件宏文件解密
  8. 工作队列模式(任务队列)| RabbitMQ系列(二)
  9. 0004-环保公益环保宣传PPT模板免费下载
  10. 自动化测试工程师的发展前景怎么样?好不好?