返回介绍

3.2 事件表和事件处理过程

发布于 2025-03-08 15:26:41 字数 3574 浏览 0 评论 0 收藏 0

wxWidgets 事件处理系统比起通常的虚方法机制来说要稍微复杂一点,但它的一个好处是可以避免需要实现基类中所有的虚方法,因为实现所有的基类虚方法有时候是不切实际的或者是效率很低的。

每一个 wxEvtHandler 的派生类,例如 frame,按钮,菜单以及文档等,都会在其内部维护一个事件表,用来告诉 wxWidgets 事件和事件处理过程的对应关系。所有继承自 wxWindow 的窗口类,以及应用程序类都是 wxEvtHandler 的派生类。

要创建一个静态的事件表(意味着它是在编译期间创建的),你需要下面几个步骤:

  1. 定义一个直接或者间接继承自 wxEvtHandler 的类。
  2. 为每一个你想要处理的事件定义一个处理函数。
  3. 在这个类中使用 DECLARE_EVENT_TABLE 声明事件表。
  4. 在.cpp 文件中使用使用 BEGIN_EVENT_TABLE 和 END_EVENT_TABLE 实现一个事件表。
  5. 在事件表的实现中增加事件宏,来实现从事件到事件处理过程的映射。

所有的事件处理函数拥有相同的形式。他们的返回值都是 void,他们都不是虚函数,他们都只有一个事件对象作为参数。(如果你熟悉 MFC,这可能会让你觉得轻松,因为在 MFC 中消息处理函数并没有一个统一的形式。)这个事件对象的类型是随这个处理函数要处理的事件的变化而变化的。例如简单控件(比如按钮)的命令处理函数和菜单命令的处理函数的参数都是 wxCommandEvent 类型,而 size 事件(这个事件通常是由用户改变窗口的客户区尺寸而引起的)处理函数的参数则是 wxSizeEvent 的类型。不同的事件参数类型可以调用的方法也不相同,通过这些方法,你可以获得事件产生的原因以及产生这个事件的控件的值的改变情况(比如,文本框中的值的改变)。当然最简单的情形是你完全不需要访问这个参数的任何方法,比如按钮点击事件。

让我们来扩展一下前一章中的例子,来增加一个窗口大小改变事件的处理和一个确定按钮的处理。下面是扩展以后的 MyFrame 的定义:

class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString& title);
    void OnQuit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);
    void OnSize(wxSizeEvent& event);
    void OnButtonOK(wxCommandEvent& event);
private:
    DECLARE_EVENT_TABLE()
};

增加菜单项的代码和前一章的代码类似,而在 frame 窗口增加一个按钮的代码也只需要在 MyFrame 的构造函数中增加下面的代码:

wxButton* button = new wxButton(this, wxID_OK, wxT("OK"),
                                   wxPoint(200, 200));

类似的,在事件表中也需要相应的增加事件映射宏:

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU     (wxID_ABOUT,     MyFrame::OnAbout)
    EVT_MENU     (wxID_EXIT,      MyFrame::OnQuit)
    EVT_SIZE     (                MyFrame::OnSize)
    EVT_BUTTON   (wxID_OK,        MyFrame::OnButtonOK)
END_EVENT_TABLE()

当用户点击关于菜单或者退出菜单的时候,这个事件被发送到 frame 窗口,而 MyFrame 的事件表告诉 wxWidgets,对于标识符为 wxID_ABOUT 的菜单事件应该发送到 MyFrame::OnAbout 函数,而标识符为 wxID_EXIT 的菜单事件应该发送到 MyFrame:: OnQuit 函数。换句话说:当事件处理循环处理这两个事件的时候,相应的函数将会以一个的 wxCommandEvent 类型的参数被调用。

EVT_SIZE 事件映射宏不需要标识符参数因为这个事件只会被产生这个事件的控件所处理。

EVT_BUTTON 这一行将导致当 frame 窗口及其子窗口中标识符为 wxID_OK 的按钮被点击的时候,OnButtonOK 函数被调用。这个例子表明,事件可以不必被产生这个事件的控件所处理。让我们假定这个按钮是 MyFrame 的子窗口。当按钮被点击的时候,wxWidgets 会首先检查 wxButton 类是否指定了相应的处理函数,如果找不到,则在其父亲的类所属的事件表中进行查找,在这个例子中,按钮的父亲是 MyFrame 类型的一个实例,在其事件表中指明了一个对应的处理函数,因此 MyFrame::OnButtonOK 函数就被调用了。类似的搜索过程不光发生在窗口控件的父子继承树中,也发生在普通的类继承关系中,这意味着你可以选择在哪里定义事件的处理函数。举例来说,如果你设计了一个对话框,这个对话框需要响应的类似标识符为 wxID_OK 的 command 事件。但是你可能需要把这个控件的创建工作留给使用你的代码的其他的程序员,只要他在创建这个控件的时候使用同样的标识符,你仍然可以给这个控件为这个事件定义默认的处理函数。

下图中演示了一次普通的按钮点击事件发生以后,wxWidgets 搜索所有事件表的顺序。图中只演示了 wxButton 和 MyFrame 两次继承关系。当用户点击了确认按钮的时候,一个新的 wxCommandEvent 事件被创建,其中包含标识符 wxID_OK 和事件类型 wxEVT_COMMAND_BUTTON_CLICKED,然后这个按钮的事件表开始通过 wxEvtHandler::ProcessEvent 函数进行匹配,事件表中的每一个条目都会去尝试匹配,然后是其父类 wxControl 的事件表,然后是 wxWindow 的。如果都没有匹配到, wxWidgets 会搜索其父亲的类事件表,然后就找到了一条匹配条目:

EVT_BUTTON(wxID_OK,MyFrame::OnButtonOK)

因此 MyFrame::OnButtonOK 被调用了。

需要注意的事:只有 Command 事件(其事件类型直接或者间接的继承自 wxCommandEvent) 才会被递归的应用到其父亲的事件表。通常这是 wxWidgets 的用户经常会感到困惑的地方,因此我们把那些不会传递给其父亲的事件表的事件列举如下:wxActivate, wxCloseEvent, wxEraseEvent, wxFocusEvent, wxKeyEvent, wxIdleEvent, wxInitDialogEvent, wxJoystickEvent, wxMenuEvent, wxMouseEvent, wxMoveEvent, wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent, wxScrollWinEvent, 和 wxSysColourChangedEvent,这些事件都不会传给事件源控件的父亲。

这些事件不会传递给其父亲,是因为这些事件仅对产生这个事件的窗口才有意义,举例来说,把一个子窗口的重绘事件发送给它的父亲,其实是没有任何意义的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文