cexer

cexer
posts - 12, comments - 334, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

转帖请注明出处 http://www.cppblog.com/cexer/archive/2008/08/18/59285.html

  抛弃了上一个消息机制,因为它的实现不得不多用了几个模板函数,在使用的时候有代码膨胀的现象。虽然其程度不如 win32gui,SmartWin,不过因为本人有点极端,所以相当地不满意。于是又开始写一个新的消息机制,它的外表看起来像是 SmartWin++ 和 AWT 的混血儿。 

  SmartWin++ 的想法极富创意,但是其实现却不怎么漂亮,就像鲁迅先生说的:离的越近,伤痕和不足越容易看见。或者更专业一点,借用一个朋友的 QQ 签名说的:每个整洁的接口后面,都有一个龌龊的实现。仔细看过它的的龌龊实现之后,我想大概是永远不会用它写程序了,不过同样要对作者的灵感表示敬佩和感谢。自己写这个框架到后来,竟然也有向 SmartWin++ 靠拢的趋势。

  AWT 的接口和实现很漂亮,不过看它在 C++ 上的这个实现,因为 C++ 与 java 一些语言特性上的差别,导致这个照搬过来的实现并不适用于 C++,一是性能很低,二是使用起来会比较麻烦。大概是因为作者要靠虑跨平台的因素,所以缚手缚脚的没有能放开写。其实在这个基础上好好地优化改进一下,会是个很不错的框架。

  自己的这个框架的消息机制是介于 SmartWin++ 的 Aspects 与 AWT 的 Listener 这间的一种机制。底层实现很简单,在上面再包装出一些 Listener 。这些 Listener 的结构看起来像 AWT 的 Listener,完成的功能却是 SmartWin++ 的 Aspects 的功能。可以对比一下看看


AWT ( C++的一个移植版)

  比如说在 AWT 当中鼠标相关的 Listener 大概是这样的:

   1: class MouseListener : public EventListener 
   2: {
   3:  
   4: public:
   5:  
   6:     virtual void mouseClicked( MouseEvent& ) = 0;
   7:     virtual void mousePressed( MouseEvent& ) = 0;
   8:     virtual void mouseReleased( MouseEvent& ) = 0;
   9:     virtual void mouseEntered( MouseEvent& ) = 0;
  10:     virtual void mouseExited( MouseEvent& ) = 0;
  11:  
  12: };

 
  如果一个类想处理自己的鼠标消息,则必须在这个类上派生,然后调用addListener 将自己添加进 Listeners 当中即可。比如说要写一个按钮类 Button* button,它要处理自己的鼠标按下的消息,代码大概会是这个样子:

   1: class Button:public Component,
   2:             ,public MouseListener  // 从 MouseListener 接口派生
   3:             ,public SomethingElse
   4: {
   5:     Button()
   6:     {
   7:         addMouseListener( this );
   8:  
   9:         // do something else
  10:     }
  11:  
  12:     virtual void mousePressed( MouseEvent& me )
  13:     {
  14:         // button is pressed
  15:         // do something
  16:     }
  17: };


  如果还有外部的类想要处理这个按钮的鼠标按下消息,那么这个外部的类也必须从 MouseListener 派生,然后将自己添加进按钮的 Listeners 当中。代码大概像这个样子:

   1: // 从 MouseListener 接口派生
   2: class MouseEventTester:public MouseListener,public SomethingElse
   3: {
   4:     // 实现 mousePreseed 函数
   5:     virtual void mousePressed( MouseEvent& )
   6:     {
   7:         // mouse is pressed
   8:         // do something;
   9:     }
  10: };
  11:  
  12: // 添加到按钮的鼠标 Listener 列表当中
  13:  
  14: Button* button = new Button();
  15: button->addMouseListener( new MouseEventTester() );


  AWT 都是使用虚函数实现的,其性能上会有一些问题,谁叫是从 java 移植的呢。而且消息处理函数的名字被限定死了,至少那些有特定名字习惯(比如很多人写的函数非得用大写字母开始)的程序会一定不会满意的。另外,消息的种类也被接口限制死了,除了接口提供的 mousePressed,mouseClicked 等消息的函数,要添加其它消息的处理能力就比较麻烦。


SmartWin++

  再看看 SmartWin++ Aspect 方式。SmartWin++ 关于鼠标消息的 Aspect 像这个样子:

   1:  
   2: template</*一大堆模板参数*/>
   3: class AspectMouseClicks
   4: {
   5:     void onLeftMouseUp( Handler eventHandler );
   6:     void onLeftMouseUp( Handler eventHandler);
   7:  
   8:     void onLeftMouseDown( Handler eventHandler);
   9:     void onLeftMouseDown( Handler eventHandler);
  10:  
  11:     void onMouseMove( Handler eventHandler);
  12:     void onMouseMove( Handler eventHandler);
  13:  
  14:     // 其它一些鼠标消息
  15:     // .....
  16:  
  17: protected:
  18:     virtual ~AspectMouseClicks()
  19:     {}
  20: };


  还是写一个按钮,它想处理自己的鼠标按下消息,代码大概像这样:

   1: class Button:public AspectMouseClicks</*一大堆参数*/>
   2: {
   3:     Button()
   4:     {
   5:         onLeftMouseUp( &Button::mousePressed );
   6:     }
   7:     
   8:     void mousePressed( MouseEvent& me )
   9:     {
  10:         // mouse is pressed
  11:         // do something
  12:     }
  13: };


  而因为 SmartWin++ 的设计上的问题,除了按钮本身之外,仅有按钮的祖先窗口(包括父窗口)对象也能够处理它的鼠标消息。比如一个窗口 TestWindow,在它之上创建了按钮 Button,则这个按钮的消息要么被 TestWindow 处理,要么被自己处理,不能被其它的外部类或函数处理。TestWndow 当中处理这个按钮的鼠标按下的消息的代码大概像这样:

   1: class TestWindow:public Something
   2: {
   3:  
   4: public:
   5:  
   6:     TestWindow()
   7:     {
   8:         Button* button = createButton();
   9:         button->onLeftMouseUp( &TestWindow::mousePressed );
  10:     }
  11:  
  12:     void mousePressed( MouseEvent& me )
  13:     {
  14:         // mouse is pressed
  15:         // do something
  16:     }
  17: };


  SmartWin++ 表面上看起来是使用的函数指针,效率应该会比 AWT 的虚函数高一些,其它不然。看看上面的代码,指定消息处理函数的时候,并没有把消息处理函数的拥有者的指针传进去。因此在用函数指针调用函数的时候,SmartWin++ 框架为了寻找到函数的拥有者指针,用了很龌龊低效的的一个方法,也正是这样的方法,使得父窗口类之外的其它类或函数不可能参与到消息的处理当中来。

自己的机制:
  
  像上面说的,自己新写的这个消息机制介于 AWT 的 Listeners 与 SmartWin 的 Aspects 之间。这个框架当中的MouseListener 的定义看起来像这样:

   1: <typename TImpl>
   2: class MouseListener
   3: {
   4:  
   5: public:
   6:  
   7:     void onMouseClicked( TOwner* owner,MemberHandler handler );
   8:     void onMousePressed( TOwner* owner,MemberHandler handler );
   9:     void onMouseReleased( TOwner* owner,MemberHandler handler );
  10:     void onMouseDblclk( TOwner* owner,MemberHandler handler );
  11:     void onMouseEntered( TOwner* owner,MemberHandler handler );
  12:     void onMouseExited( TOwner* owner,MemberHandler handler );
  13:     void onMouseMoved( TOwner* owner,MemberHandler handler );
  14:  
  15: };


  有鼠标消息的 GUI 类都已经从这个 Listener 继承,比如说如果要写一个按钮类,想要它有响应鼠标按下消息的能力,则代码大概是这样:

   1:  
   2: // 从 MouseListener 继承
   3: class Button:public MouseListener<Button>
   4:             ,public SomethingElse
   5: {
   6:  
   7: public:
   8:  
   9:     Button()
  10:     {
  11:         onMousePressed( this,&Button::mousePressed );
  12:     }
  13:     
  14:     void mousePressed( UINT keys,POINT cursor )
  15:     {
  16:         // mouse is pressed
  17:         // do something
  18:     }
  19: };


  因为 Button 已经从 MouseListener 派生,所以所有它的祖先窗口和父窗口都能用 MouseListener 的函数 onMousePressed 来注册自己的处理函数:

   1: class TestWindow
   2: {
   3:  
   4: public:
   5:  
   6:     TestWindow()
   7:     {
   8:         Button* button = new Button();
   9:         button->onMousePressed( this,&TestWindow::mousePressed );
  10:     }
  11:  
  12:     void mousePressed( Widget* source,UINT keys,POINT cursor )
  13:     {
  14:         // mouse is pressed
  15:         // do something
  16:     }
  17: };


  需要注意的,这个 mousePressed 函数多了一个 Widget* 类型的 source 参数,以便在一个函数处理多个 GUI 对象的鼠标消息的时候,用来区别鼠标消息是来自哪一个 GUI 对象。因为在按钮类当中,知道是处理的自己的消息,所以就不需要这样一个额外的参数。

  到目前为止看起来好像都跟 SmartWin++ 没有多大的区别。不过这个框架允许任何类甚至全局函数也能处理任何 GUI 对象对外公开的消息。比如说有一个额外的类想处理上面那个按钮的鼠标按下消息,其代码大概像这样:

   1: // 从 MouseListener 接口派生
   2: class MouseEventTester:public MessageListener
   3: {
   4:  
   5: public:
   6:  
   7:     void mousePressed( Widget* source,UINT keys,POINT cursor )
   8:     {
   9:         // mouse is on source,and it is pressed
  10:         // do something;
  11:     }
  12: };
  13:  
  14:  
  15: MouseEventTester* tester = new MouseEventTester();
  16:  
  17: // 添加到按钮的鼠标 Listener 列表当中
  18: Button* button = new Button();
  19: button->addListener( &tester );


  这个类不用派生自 MouseListener,而是直接派生自了底层的 MessageListener(其实 MessageListener 对外只提供了一个函数 handleMessage)

  除了上面的方式,也可以这样干,更简单一些,甚至不用任何派生。

   1: // 从 MouseListener 接口派生
   2: class MouseEventTester
   3: {
   4:  
   5: public:
   6:  
   7:     void mousePressed( Widget* source,UINT keys,POINT cursor )
   8:     {
   9:         // mouse is on source,and it is pressed
  10:         // do something;
  11:     }
  12: };
  13:  
  14:  
  15: MouseEventTester* tester = new MouseEventTester();
  16:  
  17:  
  18:  
  19:  
  20: // 直接添加消息处理函数
  21: Button* button = new Button();
  22: button->onMousePressed( tester,&MouseEventTester::mousePressed );


  也提供对全局函数的支持比如:

   1:  
   2: // 全局函数
   3: void mousePressed( Widget* source,UINT keys,POINT cursor )
   4: {
   5:     // mouse is on source,and it is pressed
   6:     // do something
   7: }
   8:  
   9:  
  10: // 直接添加为消息处理函数
  11: Button* button = new Button();
  12: button->onMousePressed( &::mousePressed );


  这个版本的消息机制虽然不如上一个消息机制方便,需要手动地消息映射,不过极大地增加了灵活性,消除了代码膨胀的问题。

  下面是一个完整的测试程序代码:

   1: // cexer
   2: #include "../../cexer/include/GUI/panel.h"
   3: #include "../../cexer/include/GUI/window.h"
   4: #include "../../cexer/include/GUI/button.h"
   5: #include "../../cexer/include/GUI/checkbox.h"
   6: #include "../../cexer/include/GUI/radiobox.h"
   7: #include "../../cexer/include/GUI/GUI.h"
   8:  
   9: using namespace cexer;
  10: using namespace cexer::gui;
  11: using namespace cexer::gdi;
  12:  
  13: // c++ std
  14: #include <iostream>
  15: using namespace std;
  16:  
  17:  
  18: class TestWindow:public Window
  19: {
  20:  
  21: public:
  22:  
  23:     TestWindow( ):Window( NULL,_T("test window") )
  24:     {
  25:         onCreated( this,&TestWindow::windowCreated );
  26:         onClosing( this,&TestWindow::windowClosing );
  27:         onDestroy( this,&TestWindow::windowDestroy );
  28:         onResized( this,&TestWindow::windowResized );
  29:         onErasing( this,&TestWindow::windowErasing );
  30:  
  31:         onMouseClicked( this,&TestWindow::mouseClicked );
  32:         onMouseEntered( this,&TestWindow::mouseEntered );
  33:         onMouseExited(  this,&TestWindow::mouseExited );
  34:         onMousePressed( this,&TestWindow::mousePressed );
  35:         onMouseReleased( this,&TestWindow::mouseReleased );
  36:     }
  37:  
  38: public:
  39:  
  40:     LRESULT windowCreated( Widget*,CREATESTRUCT& )
  41:     {
  42:         wcout<<L"创建成功!"<<endl;
  43:  
  44:         Panel* panel = new Panel( this,_T("panel") );
  45:         panel->create( _T(""),10,10 );
  46:         panel->onResized( this,&TestWindow::windowResized );
  47:  
  48:         Button* button = new Button( panel,_T("button") );
  49:         button->create( _T("测试按钮"),10,10 );
  50:         button->onClicked( this,&TestWindow::buttonClicked );
  51:  
  52:         Checkbox* checkbox = new Checkbox( panel,_T("checkbox") );
  53:         checkbox->create( _T("测试多选框"),10,40 );
  54:         checkbox->onClicked( this,&TestWindow::buttonClicked );
  55:  
  56:         
  57:         Radiobox* radiobox = new Radiobox( panel,_T("radiobox") );
  58:         radiobox->create( _T("测试单选框"),10,70 );
  59:         radiobox->onClicked( this,&TestWindow::buttonClicked );
  60:  
  61:         return 0;
  62:     }
  63:  
  64:     LRESULT windowDestroy( Widget* )
  65:     {
  66:         wcout<<L"销毁成功!"<<endl;
  67:         ::PostQuitMessage(0);
  68:  
  69:         return 0;
  70:     }
  71:  
  72:     LRESULT windowClosing( Widget* )
  73:     {
  74:         if ( IDNO == confirmBox(_T("确定关闭窗口?")) )
  75:         {
  76:             return 0;
  77:         }
  78:  
  79:         destroy();
  80:  
  81:         return 0;
  82:     }
  83:  
  84:     LRESULT windowErasing( Widget*,Canvas& canvas )
  85:     {
  86:         canvas.fillRect( clientBounds() );
  87:         canvas.drawText( 100,100,_T("测试窗口") );
  88:  
  89:         return 0;
  90:     }
  91:  
  92:     LRESULT windowResized( Widget* source,UINT,SIZE size )
  93:     {
  94:         if ( source->name() == m_name )
  95:         {
  96:             long panelWidth  = ( size.cx-20 );
  97:             long panelHeight = ( size.cy-20 );
  98:  
  99:             resizeChild( _T("panel"),panelWidth,panelHeight );
 100:         }
 101:         else if ( source->name() == _T("panel") )
 102:         {
 103:             long childWidth  = ( size.cx-20 );
 104:             long childHeight = 20;
 105:  
 106:             resizeChild( _T("button")  ,childWidth,childHeight );
 107:             resizeChild( _T("checkbox"),childWidth,childHeight );
 108:             resizeChild( _T("radiobox"),childWidth,childHeight );
 109:         }
 110:         
 111:         return 0;
 112:     }
 113:  
 114:     LRESULT buttonClicked( Button* button )
 115:     {
 116:         messageBox( button->text() + _T("被点击了!") );
 117:         return 0;
 118:     }
 119:  
 120:  
 121:     LRESULT mouseClicked( Widget*,UINT,POINT cursor)
 122:     {
 123:         wcout<<L"鼠标点击\t"<<L"位置";
 124:         wcout<<L"("<<cursor.x<<L","<<cursor.y<<L")"<<endl;
 125:         return 0;
 126:     }
 127:  
 128:     LRESULT mouseEntered( Widget*,UINT,POINT cursor )
 129:     {
 130:         wcout<<L"鼠标进入\t"<<L"位置";
 131:         wcout<<L"("<<cursor.x<<L","<<cursor.y<<L")"<<endl;
 132:         return 0;
 133:     }
 134:  
 135:     LRESULT mouseExited( Widget*,UINT,POINT cursor )
 136:     {
 137:         wcout<<L"鼠标离开\t"<<L"位置";
 138:         wcout<<L"("<<cursor.x<<L","<<cursor.y<<L")"<<endl;
 139:         return 0;
 140:     }
 141:  
 142:     LRESULT mousePressed( Widget*,UINT,POINT cursor )
 143:     {
 144:         wcout<<L"鼠标按下\t"<<L"位置";
 145:         wcout<<L"("<<cursor.x<<L","<<cursor.y<<L")"<<endl;
 146:         return 0;
 147:     }
 148:  
 149:     LRESULT mouseReleased( Widget*,UINT,POINT cursor )
 150:     {
 151:         wcout<<L"鼠标释放\t"<<L"位置";
 152:         wcout<<L"("<<cursor.x<<L","<<cursor.y<<L")"<<endl;
 153:         return 0;
 154:     }
 155:  
 156: };
 157:  
 158:  
 159:  
 160: int _tmain( int argc,TCHAR** argv )
 161: {
 162:     CEXER_GUI_THREAD();
 163:  
 164:     wcout.imbue( std::locale("") );
 165:  
 166:     TestWindow* window = new TestWindow();
 167:     window->create();
 168:  
 169:     return runGUIthread();
 170: }

Feedback

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-08-19 12:37 by Touchsoft
只看了开头的鼠标消息定义
如果一个类想处理自己的鼠标消息,则必须在这个类上派生,然后调用addListener
像Decorator Pattern。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-08-19 14:03 by 空明流转
函数指针比虚方法更慢。。。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-08-19 16:11 by cexer
@空明流转
的确是半斤八两,不过虚函数还得查一下虚表,生成的汇编代码会有多几个寄存器操作,而静态的函数指针是直接call地址的。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-08-20 01:24 by 陈梓瀚(vczh)
很好,已经快接近我那个了……

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-08-24 16:06 by dell
每个整洁的接口后面,都有一个龌龊的实现。真是深刻。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2008-12-22 01:45 by null
to cexer

你都是怎么看源代码的 我也下了个超小型的GUI库 不知道从哪看起

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2009-10-12 00:56 by XML
请教一下,你的MemberHandler 是怎么定义的?是如何让onXXEvent接收任意类的成员函数的?

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2009-11-16 00:57 by 暗涌
消息处理函数有点像C#中的EventHandler,总是把消息的发送者传给处理函数。把发送者类型放到模板参数里是个不错的办法,有点像Singleton的模板。。。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2009-11-17 17:13 by cexer
@null
读书千遍其义自现啊,最主要是闷着看代码。
从示例代码,最高层开始往底层追溯

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2009-11-17 17:13 by cexer
@XML
那个代码找不到了,不过实现还记得,以后会写一下实现。

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2009-11-17 17:14 by cexer
@暗涌
嗯呵呵

# re: 抛弃了上一个 GUI 消息机制,重写了一个更灵活高效的  回复  更多评论   

2010-02-22 21:01 by jom
能写自己的GUI框架,不错啊,呵呵。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理