wxWidgets动态事件表
使用动态事件映射方法的原因,可能是你想在程序运行的不同时刻使用不同的映射关系,或者因为你使用的那种语言(例如python)不支持静态映射,或者仅仅是因为你更喜欢动态映射。因为动态映射的方法可以使你更精确的控制事件表的细节,你甚至可以单独的将事件表中的某一个条目在运行期打开或者关闭,而前面说的PushEventHandler和PopEventHandler的方法只能针对整个事件表进行处理。除此以外,动态事件处理还允许你在不同的类之间共享事件函数。
——《WxWidgets跨平台GUI开发》
导言
在wxWidgets中,相对于静态事件表那种僵死并且不知其所以然的方法,我更喜欢动态事件表,亲自Connect,还可以随时Disconnect。
但是,在写动态事件表时,会遇到一个问题,wxWidgets的官方文档中的事件处理部份,对于静态事件表所需的各种事件类型的宏叙述详尽,却对动态事件表所需的事件类型语焉不详,这给我们的使用带来了麻烦。需要的知识一方面零星分布于wx文档中,另一方面被冗长的代码掩映在
大部份写GUI常用的wx类(包括窗口、对话框、控件)都继承于三个类:wxWindowwx、wxEvtHandler、wxObject。因此,大多数情况下,这三个类的成员函数是我们可以顺手牵过来用的。动态事件表的使用中,最为重要的函数Connect和Disconnect就是 wxEvtHandler的成员函数,我们可以牵过来给我们手头这个要处理事件的wx类用。
先看看官方文档里对Connect函数的介绍(我对其进行了翻译、精简,有时,为了解释的明晰,作一些补充说明):
#TRANSLATE BEGIN
wxEvtHandler::Connect
Connect函数被重载了三次,各有各的用途。
第一个版本:范围捕杀 ( [id, lastid] 且 eventType)
void Connect(int id, int lastId, wxEventType eventType, wxObjectEventFunction function, wxObject* userData = NULL, wxEvtHandler* eventSink = NULL)
第二个版本:精确狙击(id 且 eventType)
void Connect(int id, wxEventType eventType, wxObjectEventFunction function, wxObject* userData = NULL, wxEvtHandler* eventSink = NULL)
第三个版本:分门别类 (仅 eventType)
void Connect(wxEventType eventType, wxObjectEventFunction function, wxObject* userData = NULL, wxEvtHandler* eventSink = NULL)
该函数动态地将所给事件处理函数 与 EventHandler、ID 甚至事件类型联系起来。这是静态事件表的一种替代选择。
按:EventHandler直译为事件处理器,或可译为“事件手柄”(生动地模仿句柄)?后文使用“事件手柄”。
参数意义:
id
你要和事件处理函数联系起来的ID(可以是窗口ID、菜单ID、控件ID)。对于没有这个参数的重载版本,id被默认设为 wxID_ANY。
当和lastid连用时,表达的是一个ID范围,即大小介于id和last id之间的所有ID,都会被Connect函数与事件处理函数联系起来。
按:实在没必要连“ID”都译成“标识符”……其实看技术文档时,我最头痛的是一堆汉字堆在那里,包括看数学书时……
lastId
参见id中的介绍。
eventType
你要和事件处理函数联系起来的Event Type。按:Event Type直译为“事件类型”并不恰当,因为事件类型是指形如wxMouseEvent这样的东西,而这里所指,是形如wxEVT_MOTION这样的东西。准确地说,应该称之为“事件标识符”或“事件ID”。下文使用“事件ID”。一个事件类型里,会有若干事件ID,比wxMouseEvent里除了wxEVT_MOTION,还有wxEVT_LEFT_DOWN等等等事件ID。 因此可以把事件ID作为对事件类型的一个细分。
function
事件处理函数。注意这个函数应当被显式转换为正确的类型。对于类型为wxFooEvent的事件,转换使用宏wxFooEventHandler。
userData
你要和事件表项联系起来的数据。(暂时我还不知道这个有什么用)
eventSink
告诉Connect函数,你要调用的事件处理函数是谁的成员函数。如果该参数为 NULL, 那么Connect函数将使用this指针 。按:正是这个参数允许了我们在不同类中共享事件处理函数。
例子:frame->Connect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyFrame::OnQuit) );对例子的解释:wxID_EXIT是之前赋给了某一个菜单项的ID。当该菜单被选择时,会产生一个ID为wxEVT_COMMAND_MENU_SELECTED的事件。因此MyFrame::OnQuit在被显式转换为wxCommandEventHandler型的“事件手柄”之后,被Connect拉过来处理该事件。 #TRANSLATE END
就不再翻译Disconnect函数的文档了,因为除了函数名不同,它的所有参数和Connect是一模一样的。它的用途就是断开Connect所建立起来的联系。
实践中写事件处理的时候,通常使用的是Connect的后两种重载版本。参数userData和eventSink的使用是比较少的,而且也有默认值,一般不去理它就可以了。
参数id是你赋给产生了这个事件的窗口、菜单或控件的,你自己心里是清楚的。问题顶多是,你需要使用系统默认的那些ID(比如上面例子里的 wxID_EXIT),而你不知道哪个还哪个。那些ID的列表在官方文档里是有的,不过为了本文作为手册的完整性,将在本文最后给出。
有时是不需要id的,比如鼠标移动的事件:
frame->Connect(wxEVT_MOTION,wxMouseEventHandler(MyFrame::OnMouseMove));
直接把事件ID与事件手柄联系起来。 参数function的问题大一些,就是这个显式强制类型转换的这个Handler,具体叫wx什么EventHandler呢?这个问题也好解决,因为官方文档里的 Classes By Category里的Event小节已经给出了形如wxFooEvent这样的事件类型的列表,只需要在相应的事件类型后面加上Handler就可以了。同样为了本文作为手册的完整性,将在本文后面给出全部Handler的列表。
最大的问题出在参数eventType上。形如wxEVT_MOTION、wxEVT_COMMAND_MENU_SELECTED这样的事件ID的名字,我们从何得知?这些奇形怪状的名字,纵然我们英语很强,也未必能造出和wxWidgets定义的一模一样的名字啊。比如写惯MFC的同好们很容易将鼠标移动事件ID写为wxMOUSEMOVE或wxMOUSE_MOVE,可这却是错的。最糟糕的是,文档中没有提供这些名字的列表!
有些同好可能发现了,对大部份事件类型的静态事件表的宏的说明中,包含了这方面的重要信息,例如wxPaintEvent里:
EVT_PAINT(func) Process a wxEVT_PAINT event.
左边是静态事件表需要的,右边是动态事件表需要的。左手静态,右手动态,好潇洒啊!然而,倘若你要处理窗口关闭事件(假设该事件的产生不是通过菜单选择,而是点窗口右上角的红叉叉),你跑到wxCloseEvent那里一看:
EVT_CLOSE(func) Process a close event, supplying the member function. This event applies to wxFrame and wxDialog classes.
你晕了……为什么文档编写者连这都不肯告诉你?!于是你尝试着用wxEVT_CLOSE,编译器告诉你不对。已经习惯了没事翻翻头文件的你就跑到
所幸这个情况并没有出现在大多数基本的事件里,但实际中总会需要处理那些不那么基本的事件的,每次都在这个细节上卡这么久的壳太不划算了。所以我把事件 ID列表从
。