转帖请注明出处 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: }