|
Posted on 2008-08-07 18:00 cexer 閱讀(2779) 評論(6) 編輯 收藏 引用 所屬分類: GUI
轉帖請注明出處 http://www.shnenglu.com/cexer/archive/2008/08/07/58262.html
有時候在界面上的一系列相關控件,它們作為一組相互協作提供一個功能,則在事件處理的時候,給這一組的控件僅提供一個事件處理程序,要比給每一個單獨的控件都提供一個事件處理程序要簡單得多,邏輯也更清楚。一個簡單的例子就是 windows 自帶的計算器程序界面上的按鈕 1,2,……,9。
一般來說,這樣的控件的 ID 都定義是連續的,如果是這樣,那么 GUI 框架就有可能提供這樣一個接口,客戶端只需要對這個接口提供控件組的開始 ID 和 結束 ID (以及通知消息的 ID),GUI 框架就能自動地把這一組控件的消息映射到某一個消息處理函數。
MFC:
MFC 提供了這樣一個接口,就是 COMMAND_RANGE_HANDLER 宏。使用方法很簡單,用計算器的按鈕作為例子,假設計算器的數字按鈕從 0 到 9 的 ID 分別定義為從 IDB_0 到 IDB_9的十個常數,并且它們連續,遞增的。先定義一個消息處理函數:
1: void numberClicked( UINT id,UINT code )
2: {
3: int number = id - IDB_0;
4:
5: // do something else
6: // ....
7: }
然后就以用宏來將數據按鈕的消息映射到這個函數了:
1: COMMAND_RANGE_HANDLER( IDB_0,IDB_9,numberClicked )
Win32GUI:
Win32Gui 也提供了一這樣的接口,使用方法如下(不過這個消息處理函數好像無法知道到底是誰被點擊了):
1: handle_event on_number_clicked()
2: {
3: // don't know who's been clicked
4: // do something
5: // ...
6:
7: return command_range<IDB_0,IDB_9,BN_CLICKED>().HANDLED_BY(&me::on_number_clicked);
8: }
SmartWin++:
SmartWin++ 沒有提供這樣的接口,所以如果要處理那些數字按鈕的點擊事件就比較麻煩,必須得這樣:
1: void nubmerClicked( WidgetButtonPtr button )
2: {
3: int number = button->getID() - IDB_0;
4:
5: // do something else
6: // ....
7: }
8:
9: void initAndCreate()
10: {
11: button0->onClicked( &This::numberClicked );
12: button1->onClicked( &This::numberClicked );
13: //.....
14: button9->onClicked( &This::numberClicked );
15: }
如果按鈕數目比較多,比如說程序同時需要能輸入字母和數字,那么按鈕就至少有三十多個,這個情況下,除非是CTRL+C,CTRL+V 的愛好者,否則都不會愿意這樣一個個地手動映射。不過因為 ID 是連續的,當數目太多的時候,可以利用循環來映射:
1: void initAndCreate()
2: {
3: for ( UINT i=IDB_0; i<=IDB_9; ++i )
4: {
5: WidgetButtonPtr button = this->subclassButton( i );
6: button->onClicked( &This::numberClicked );
7: }
8: }
QT: QT 類似 SmartWin,我沒有找到這樣的接口,用 QT 來實現將這些數字按鈕的點擊消息映射到消息處理函數的代碼類似這樣(似乎對于按鈕的 clicked() 信號,這樣多個按鈕的點擊事件用一個函數處理,在函數當中也無法得知究竟是誰被點擊了,不過其它帶參數的信號大概有些是可以的):
1: void numberClicked()
2: {
3: // don't known who's been clicked
4: // do something
5: // ....
6: }
7:
8:
9: QPushButton* button0;
10: QPushButton* button1;
11: //....
12: QPushButton* button9;
13:
14:
15: connect( button0,SIGNAL(clicked()),this,SLOT(numberClicked()) );
16: connect( button1,SIGNAL(clicked()),this,SLOT(numberClicked()) );
17: //....
18: connect( button9,SIGNAL(clicked()),this,SLOT(numberClicked()) );
自己寫的 GUI 框架:
今天下午我在用自己的 GUI 框架寫一個類似計算器測試程序的時候,發現了這種 Range 消息映射功能的重要性,于是看了看各框架對于這個功能的支持。
自己的 GUI 框架不支持,不過研究了一下,寫了一個模板:
1: template<typename TCommand,WORD t_firstId,WORD t_lastId,WORD t_code>
2: class RangeCommandBase
3: {
4:
5: protected:
6:
7: typedef RangeCommandBase<TCommand,t_firstId,t_lastId,t_code> _BaseRangeCommand;
8:
9: public:
10:
11: UINT mess;
12: WPARAM wpar;
13: LPARAM lpar;
14: WORD id;
15: WORD code;
16: DWORD data;
17:
18: // .....
19: }
如果需要將一組控件(ID 從 t_firstId 到 t_lastId )的某個通知消息作統一處理(即使用一個特定的消息處理函數),只需要將消息類從這個模板派生即可,然后我寫了一個模板作為一般的“范圍 ID 的命令消息”類的實現:
1: template<WORD t_firstId,WORD t_lastId,WORD t_code>
2: class RangeCommand:public RangeCommandBase<RangeCommand<t_firstId,t_lastId,t_code>,t_firstId,t_lastId,t_code>
3: {
4: public:
5: template<typename TWidget>
6: RangeCommand( TWidget*,UINT mess,WPARAM wpar,LPARAM lpar )
7: :_BaseRangeCommand( mess,wpar,lpar )
8: {
9:
10: }
11:
12: };
利用這個模板來實現用計算器程序當中 IDB_0 到 IDB_9 的按鈕的點擊事件處理的代碼像這樣:
1: LRESULT onCommand( RangeCommand<IDB_0,IDB_9,BN_CLICKED>& numberClicked )
2: {
3: int number = numberClicked.id - IDB_0;
4:
5: // do something else
6: // ......
7:
8: return numberClicked.handled(this);
9: }
如果不想每次都寫 BN_CLICKED,那么可以從 RangeCommandBase 的基礎上派生,派生的時候提供給該模板 BN_CLICKED 作為 t_code 參數即可,比如:
1: template<WORD t_firstId,WORD t_lastId>
2: class RangeButtonClicked:public RangeCommandBase<RangeButtonClicked<t_firstId,t_lastId>,t_firstId,t_lastId,BN_CLICKED>
3: {
4: public:
5: template<typename TWidget>
6: RangeButtonClicked( TWidget*,UINT mess,WPARAM wpar,LPARAM lpar )
7: :_BaseRangeCommand( mess,wpar,lpar )
8: {
9:
10: }
11:
12: };
利用這個模板來實現用計算器程序當中 IDB_0 到 IDB_9 的按鈕的點擊事件處理的代碼像這樣:
1: LRESULT onCommand( RangeButtonClicked<IDB_0,IDB_9>& numberClicked )
2: {
3: int number = numberClicked.id - IDB_0;
4:
5: // do something else
6: // ...
7:
8: return numberClicked.handled(this);
9: }
Feedback
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理)[未登錄] 回復 更多評論
2008-08-08 11:50 by
Qt中可以采用 QSignalMapper.
void numberClicked(int id) {}
QPushButton* button0; QPushButton* button1;
QSignalMapper* map = new QSignalMapper(this); connect(button0, SIGNAL(clicked()), signalMapper, SLOT(map())); connect(button1, SIGNAL(clicked()), signalMapper, SLOT(map())); map->setMapping(button0, 0); map->setMapping(button1, 1);
connect(map, SIGNAL(mapped(int)), this, SIGNAL(numberClicked(id)));
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理)[未登錄] 回復 更多評論
2008-08-08 11:55 by
上面有些錯誤:
QSignalMapper* map = new QSignalMapper(this); connect(button0, SIGNAL(clicked()), map, SLOT(map())); connect(button1, SIGNAL(clicked()), map, SLOT(map())); map->setMapping(button0, 0); map->setMapping(button1, 1);
connect(map, SIGNAL(mapped(int)), this, SIGNAL(numberClicked(int)));
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理) 回復 更多評論
2008-08-08 12:56 by
@eXile
多謝,我對QT用得不多,我會把你這個方法更新到日志上去.
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理) 回復 更多評論
2008-08-09 02:52 by
凡是做GUI最終都還是要做GUI Editor的。讓他去生成代碼,所有問題都不是問題。
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理) 回復 更多評論
2008-08-09 11:56 by
@陳梓瀚(vczh)
GUI 太復雜了,GUI Editor 不可能完成所有的工作.而且有了這些接口,GUI Editor 的相關部分要容易實現得多.
# re: 各 GUI 框架的 COMMAND_RANGE_HANDLER(范圍 ID 的命令消息統一處理) 回復 更多評論
2008-08-09 18:50 by
事實上非編譯時期綁定的事件處理函數更加易于給GUI Editor提供便利。
|