学习建议:先看视频一“基础知识”,然后阅读后续文字教程,以及视频二 “上机操作”,最后动手完成视频二中所演示的练习(使用 wxSmith 绑定、手工动态绑定、手工静态绑定)。
1. 视频1:基础知识
2. 事件绑定
在 GUI (图形用户界面) 应用中,用户在某个 UI 控件上施加某一类型的操作,于是程序通过执行相应的代码(通常是调用某个函数)以给出反应。这其中,需要将以下三者关联起来:
- UI 控件(通常用一个编号,即 ID 表示,本质是整数);
- 操作类型(通常使用枚举表示,本质是整数);
- 事件响应函数(通常使用函数地址表示);
这个关联操作,就做 UI 控件的事件绑定。
具体怎么实现,和现实生活一样,建表,然后查表:
控件ID(整数) | 事件类型(整数) | 事件响应函数(地址) |
---|---|---|
998 | 鼠标左键单击 | Dlg::OnAboutLeftClick |
998 | 鼠标右键单击 | Dlg::OnAboutRightClick |
999 | 鼠标左键双击 | Dlg::OnAboutDoubleClick |
稍加观察应能发现,在示例表中,控件ID + 事件类型 才是行数据的唯一值,可作为搜索的键值,实际实现,通常只以控件 ID 为键值,找到特定控件后,再于其内查找不同事件类型所对应的不同的响应函数。
具体如何表达这张表呢?基本思路是,首先有个结构,用来表示表中某一行的数据,比如:
struct EventItem // 相于一行
{
int evtID; // 控件ID
int evtType; // 事件类型,通常会定义为 enum
EvtFunction* evtFunction; // 函数指针(指向响应函数)
};
这算是一行(三列),那用什么做表格?可以是数组——但查询效率不高;可以是二叉树(或有序数组),从而拥有 O(log n) 的复杂度,但拥有复杂度为 O(1) 的查询效率的,是哈希表(hash-table,比如 C++标准库的 std::unorder_map)。
当然,由于历史原因,wxWidgets 可能并没有直接使用 C++ 标准库。
对应到 wxWidgets 应用的代码,首先会在窗体类定义尾部,发现一行:
DECLARE_EVENT_TABLE()
它实际“偷偷地”在当前类中,添加了两个重要的静态常量数据:一个就是上述的 “事件哈希表”,wxWidgets 称之为 “事件入口表”,表的成员(“行”)是结构体 “wxEventTableEntry”,大家可以看一下它的构造函数声明:
wxEventTableEntry(const int& evType, int winid, int idLast,
wxObjectEventFunction fn, wxObject *data);
其中:
- evType 事件类型
- winid 控件ID
- fn 事件响应类型,请记住它的类型是 wxObjectEventFunction
- data 事件附加数据
显然,和我们示例用的 “struct EventItem” 相当的接近。
3. 静态绑定 / 动态绑定
在代码编译时,就能确定的关系绑定,叫静态绑定;自然,需要等代码编译后,并且执行起来才完成的绑定,叫动态绑定。
如何才能在编译时就确定绑定关系?其实就是直接用代码,在事件表中填充好一个个数据(绑定关系)。因此,任何需要通过运行时才能确定的事件绑定关系,都无法以静态方式实现绑定;非要用静态,解决方法是:固定绑定一个事件函数,然后在该函数内再做判断,通过条件分支实现分流。比如,同一个按钮,希望普通用户和VIP 用户走不同的逻辑,静态绑定代码如下(仅为示意):
// 静态绑定
void Dlg::OnButtonAClick(wxCommandEvent& evt)
{
(this->VIP)? this->onVIPButtonA(evt) : this->onUserButtonAClick(evt);
}
使用动态绑定,就可以在用户登录时(很可能也是个事件响应函数),再进行绑定:
void Dlg::OnUserLoginOK(bool isVIP)
{
(isVIP) ?
Conect(ID_BUTTONA, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&Dlg::OnVIPButtonAClick); // 绑定VIP函数
: Conect(ID_BUTTONA, wxEVT_COMMAND_BUTTON_CLICKED, (wxObjectEventFunction)&Dlg::OnUserButtonAClick); // 绑定普通用户函数
}
在复杂系统中,可能需要基于更复杂的业务逻辑,以在运行期确实事件绑定关系。除此之外,复杂系统往往需要使用 “MVC” 的架构思想,将 V(view / 界面) 和 C(controller / 逻辑控制)加以拆分,此时会出现需要在某个窗体(隶属 View)的类中,绑定另外一个名为 “XXXController” 的类的方法,即:事件的跨类绑定,此时的唯一选择(至少在 wxWidgets中)是动态绑定。
不难得出结论:静态绑定性能会好一点点(毕竟编译之后,数据就已准备好),但灵活性没有动态绑定高。