|
Posted on 2008-08-07 18:00 cexer 阅读(2759) 评论(6) 编辑 收藏 引用 所属分类: GUI
转帖请注明出处 http://www.cppblog.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提供便利。
|