教程
事件的静态与动态绑定
校园
教程
心得
关于
冒泡
个人中心
事件的静态与动态绑定
课文封面

本课含两个部分(对应两个视频):

  1. 理解事件、事件表、事件绑定,以及何为静态绑定,何为动态绑定,二者各自的优缺点是什么;
  2. 上机实践如何绑定事件,包括:① 使用 GUI Builder 绑定,② 手工动态绑定,③ 手工静态绑定。

学习建议:先看视频一“基础知识”,然后阅读后续文字教程,以及视频二 “上机操作”,最后动手完成视频二中所演示的练习(使用 wxSmith 绑定、手工动态绑定、手工静态绑定)。

1. 视频1:基础知识

阅读笔记

2. 事件绑定

在 GUI (图形用户界面) 应用中,用户在某个 UI 控件上施加某一类型的操作,于是程序通过执行相应的代码(通常是调用某个函数)以给出反应。这其中,需要将以下三者关联起来:

  1. UI 控件(通常用一个编号,即 ID 表示,本质是整数);
  2. 操作类型(通常使用枚举表示,本质是整数);
  3. 事件响应函数(通常使用函数地址表示);

这个关联操作,就做 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中)是动态绑定。

不难得出结论:静态绑定性能会好一点点(毕竟编译之后,数据就已准备好),但灵活性没有动态绑定高。

阅读笔记

4. 视频2:上机实践

阅读笔记

目录