拋棄了上一個(gè) GUI 消息機(jī)制,重寫(xiě)了一個(gè)更靈活高效的
Posted on 2008-08-18 22:19 cexer 閱讀(4573) 評(píng)論(12) 編輯 收藏 引用 所屬分類: GUI轉(zhuǎn)帖請(qǐng)注明出處 http://m.shnenglu.com/cexer/archive/2008/08/18/59285.html
拋棄了上一個(gè)消息機(jī)制,因?yàn)樗膶?shí)現(xiàn)不得不多用了幾個(gè)模板函數(shù),在使用的時(shí)候有代碼膨脹的現(xiàn)象。雖然其程度不如 win32gui,SmartWin,不過(guò)因?yàn)楸救擞悬c(diǎn)極端,所以相當(dāng)?shù)夭粷M意。于是又開(kāi)始寫(xiě)一個(gè)新的消息機(jī)制,它的外表看起來(lái)像是 SmartWin++ 和 AWT 的混血兒。
SmartWin++ 的想法極富創(chuàng)意,但是其實(shí)現(xiàn)卻不怎么漂亮,就像魯迅先生說(shuō)的:離的越近,傷痕和不足越容易看見(jiàn)。或者更專業(yè)一點(diǎn),借用一個(gè)朋友的 QQ 簽名說(shuō)的:每個(gè)整潔的接口后面,都有一個(gè)齷齪的實(shí)現(xiàn)。仔細(xì)看過(guò)它的的齷齪實(shí)現(xiàn)之后,我想大概是永遠(yuǎn)不會(huì)用它寫(xiě)程序了,不過(guò)同樣要對(duì)作者的靈感表示敬佩和感謝。自己寫(xiě)這個(gè)框架到后來(lái),竟然也有向 SmartWin++ 靠攏的趨勢(shì)。
AWT 的接口和實(shí)現(xiàn)很漂亮,不過(guò)看它在 C++ 上的這個(gè)實(shí)現(xiàn),因?yàn)?C++ 與 java 一些語(yǔ)言特性上的差別,導(dǎo)致這個(gè)照搬過(guò)來(lái)的實(shí)現(xiàn)并不適用于 C++,一是性能很低,二是使用起來(lái)會(huì)比較麻煩。大概是因?yàn)樽髡咭繎]跨平臺(tái)的因素,所以縛手縛腳的沒(méi)有能放開(kāi)寫(xiě)。其實(shí)在這個(gè)基礎(chǔ)上好好地優(yōu)化改進(jìn)一下,會(huì)是個(gè)很不錯(cuò)的框架。
自己的這個(gè)框架的消息機(jī)制是介于 SmartWin++ 的 Aspects 與 AWT 的 Listener 這間的一種機(jī)制。底層實(shí)現(xiàn)很簡(jiǎn)單,在上面再包裝出一些 Listener 。這些 Listener 的結(jié)構(gòu)看起來(lái)像 AWT 的 Listener,完成的功能卻是 SmartWin++ 的 Aspects 的功能。可以對(duì)比一下看看
AWT ( C++的一個(gè)移植版)
比如說(shuō)在 AWT 當(dāng)中鼠標(biāo)相關(guān)的 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: };
如果一個(gè)類想處理自己的鼠標(biāo)消息,則必須在這個(gè)類上派生,然后調(diào)用addListener 將自己添加進(jìn) Listeners 當(dāng)中即可。比如說(shuō)要寫(xiě)一個(gè)按鈕類 Button* button,它要處理自己的鼠標(biāo)按下的消息,代碼大概會(huì)是這個(gè)樣子:
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: };
如果還有外部的類想要處理這個(gè)按鈕的鼠標(biāo)按下消息,那么這個(gè)外部的類也必須從 MouseListener 派生,然后將自己添加進(jìn)按鈕的 Listeners 當(dāng)中。代碼大概像這個(gè)樣子:
1: // 從 MouseListener 接口派生
2: class MouseEventTester:public MouseListener,public SomethingElse
3: {
4: // 實(shí)現(xiàn) mousePreseed 函數(shù)
5: virtual void mousePressed( MouseEvent& )
6: {
7: // mouse is pressed
8: // do something;
9: }
10: };
11:
12: // 添加到按鈕的鼠標(biāo) Listener 列表當(dāng)中
13:
14: Button* button = new Button();
15: button->addMouseListener( new MouseEventTester() );
AWT 都是使用虛函數(shù)實(shí)現(xiàn)的,其性能上會(huì)有一些問(wèn)題,誰(shuí)叫是從 java 移植的呢。而且消息處理函數(shù)的名字被限定死了,至少那些有特定名字習(xí)慣(比如很多人寫(xiě)的函數(shù)非得用大寫(xiě)字母開(kāi)始)的程序會(huì)一定不會(huì)滿意的。另外,消息的種類也被接口限制死了,除了接口提供的 mousePressed,mouseClicked 等消息的函數(shù),要添加其它消息的處理能力就比較麻煩。
SmartWin++
再看看 SmartWin++ Aspect 方式。SmartWin++ 關(guān)于鼠標(biāo)消息的 Aspect 像這個(gè)樣子:
1:
2: template</*一大堆模板參數(shù)*/>
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: // 其它一些鼠標(biāo)消息
15: // .....
16:
17: protected:
18: virtual ~AspectMouseClicks()
19: {}
20: };
還是寫(xiě)一個(gè)按鈕,它想處理自己的鼠標(biāo)按下消息,代碼大概像這樣:
1: class Button:public AspectMouseClicks</*一大堆參數(shù)*/>
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: };
而因?yàn)?SmartWin++ 的設(shè)計(jì)上的問(wèn)題,除了按鈕本身之外,僅有按鈕的祖先窗口(包括父窗口)對(duì)象也能夠處理它的鼠標(biāo)消息。比如一個(gè)窗口 TestWindow,在它之上創(chuàng)建了按鈕 Button,則這個(gè)按鈕的消息要么被 TestWindow 處理,要么被自己處理,不能被其它的外部類或函數(shù)處理。TestWndow 當(dāng)中處理這個(gè)按鈕的鼠標(biāo)按下的消息的代碼大概像這樣:
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++ 表面上看起來(lái)是使用的函數(shù)指針,效率應(yīng)該會(huì)比 AWT 的虛函數(shù)高一些,其它不然。看看上面的代碼,指定消息處理函數(shù)的時(shí)候,并沒(méi)有把消息處理函數(shù)的擁有者的指針傳進(jìn)去。因此在用函數(shù)指針調(diào)用函數(shù)的時(shí)候,SmartWin++ 框架為了尋找到函數(shù)的擁有者指針,用了很齷齪低效的的一個(gè)方法,也正是這樣的方法,使得父窗口類之外的其它類或函數(shù)不可能參與到消息的處理當(dāng)中來(lái)。
自己的機(jī)制:
像上面說(shuō)的,自己新寫(xiě)的這個(gè)消息機(jī)制介于 AWT 的 Listeners 與 SmartWin 的 Aspects 之間。這個(gè)框架當(dāng)中的MouseListener 的定義看起來(lái)像這樣:
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: };
有鼠標(biāo)消息的 GUI 類都已經(jīng)從這個(gè) Listener 繼承,比如說(shuō)如果要寫(xiě)一個(gè)按鈕類,想要它有響應(yīng)鼠標(biāo)按下消息的能力,則代碼大概是這樣:
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: };
因?yàn)?Button 已經(jīng)從 MouseListener 派生,所以所有它的祖先窗口和父窗口都能用 MouseListener 的函數(shù) onMousePressed 來(lái)注冊(cè)自己的處理函數(shù):
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: };
需要注意的,這個(gè) mousePressed 函數(shù)多了一個(gè) Widget* 類型的 source 參數(shù),以便在一個(gè)函數(shù)處理多個(gè) GUI 對(duì)象的鼠標(biāo)消息的時(shí)候,用來(lái)區(qū)別鼠標(biāo)消息是來(lái)自哪一個(gè) GUI 對(duì)象。因?yàn)樵诎粹o類當(dāng)中,知道是處理的自己的消息,所以就不需要這樣一個(gè)額外的參數(shù)。
到目前為止看起來(lái)好像都跟 SmartWin++ 沒(méi)有多大的區(qū)別。不過(guò)這個(gè)框架允許任何類甚至全局函數(shù)也能處理任何 GUI 對(duì)象對(duì)外公開(kāi)的消息。比如說(shuō)有一個(gè)額外的類想處理上面那個(gè)按鈕的鼠標(biāo)按下消息,其代碼大概像這樣:
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: // 添加到按鈕的鼠標(biāo) Listener 列表當(dāng)中
18: Button* button = new Button();
19: button->addListener( &tester );
這個(gè)類不用派生自 MouseListener,而是直接派生自了底層的 MessageListener(其實(shí) MessageListener 對(duì)外只提供了一個(gè)函數(shù) handleMessage)
除了上面的方式,也可以這樣干,更簡(jiǎn)單一些,甚至不用任何派生。
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: // 直接添加消息處理函數(shù)
21: Button* button = new Button();
22: button->onMousePressed( tester,&MouseEventTester::mousePressed );
也提供對(duì)全局函數(shù)的支持比如:
1:
2: // 全局函數(shù)
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: // 直接添加為消息處理函數(shù)
11: Button* button = new Button();
12: button->onMousePressed( &::mousePressed );
這個(gè)版本的消息機(jī)制雖然不如上一個(gè)消息機(jī)制方便,需要手動(dòng)地消息映射,不過(guò)極大地增加了靈活性,消除了代碼膨脹的問(wèn)題。
下面是一個(gè)完整的測(cè)試程序代碼:
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"創(chuàng)建成功!"<<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("測(cè)試按鈕"),10,10 );
50: button->onClicked( this,&TestWindow::buttonClicked );
51:
52: Checkbox* checkbox = new Checkbox( panel,_T("checkbox") );
53: checkbox->create( _T("測(cè)試多選框"),10,40 );
54: checkbox->onClicked( this,&TestWindow::buttonClicked );
55:
56:
57: Radiobox* radiobox = new Radiobox( panel,_T("radiobox") );
58: radiobox->create( _T("測(cè)試單選框"),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("確定關(guān)閉窗口?")) )
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("測(cè)試窗口") );
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("被點(diǎn)擊了!") );
117: return 0;
118: }
119:
120:
121: LRESULT mouseClicked( Widget*,UINT,POINT cursor)
122: {
123: wcout<<L"鼠標(biāo)點(diǎn)擊\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"鼠標(biāo)進(jìn)入\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"鼠標(biāo)離開(kāi)\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"鼠標(biāo)按下\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"鼠標(biāo)釋放\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: }


