原文: http://zetcode.com/gui/wxwidgets/events/

事件是每个 GUI 应用不可或缺的一部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。 当应用启动时,将创建一个主循环。 该应用位于主循环中,并等待事件生成。 当我们退出应用时,主循环退出。

定义

事件是来自底层框架(通常是 GUI 工具箱)的应用级信息。事件循环是一种程序结构,用于等待并调度器中的事件或消息。 事件循环反复查找事件以对其进行处理。调度器是将事件映射到事件处理器的过程。 事件处理器是对事件做出反应的方法。

事件对象是与事件关联的对象。 通常是一个窗口。事件类型是已生成的唯一事件。

一个简单的事件示例

在 wxWidgets 中处理事件的传统方法是使用静态事件表。这受 Microsoft 基础类(MFC)的影响。 一种更灵活,更现代的方法是使用Connect()方法。 我们在整个 wxWidgets 教程中都使用它。

事件表

在下一个示例中,我们显示一个使用事件表的示例。

button.h

  1. #include <wx/wx.h>
  2. class MyButton : public wxFrame
  3. {
  4. public:
  5. MyButton(const wxString& title);
  6. void OnQuit(wxCommandEvent& event);
  7. private:
  8. DECLARE_EVENT_TABLE()
  9. };

button.cpp

  1. #include "button.h"
  2. MyButton::MyButton(const wxString& title)
  3. : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
  4. {
  5. wxPanel *panel = new wxPanel(this, wxID_ANY);
  6. wxButton *button = new wxButton(panel, wxID_EXIT,
  7. wxT("Quit"), wxPoint(20, 20));
  8. Centre();
  9. }
  10. void MyButton::OnQuit(wxCommandEvent& WXUNUSED(event))
  11. {
  12. Close(true);
  13. }
  14. BEGIN_EVENT_TABLE(MyButton, wxFrame)
  15. EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
  16. END_EVENT_TABLE()

main.h

  1. #include <wx/wx.h>
  2. class MyApp : public wxApp
  3. {
  4. public:
  5. virtual bool OnInit();
  6. };

main.cpp

  1. #include "main.h"
  2. #include "button.h"
  3. IMPLEMENT_APP(MyApp)
  4. bool MyApp::OnInit()
  5. {
  6. MyButton *button = new MyButton(wxT("Button"));
  7. button->Show(true);
  8. return true;
  9. }

在我们的示例中,我们创建一个简单的按钮。 通过单击按钮,我们关闭应用。

  1. private:
  2. DECLARE_EVENT_TABLE()

在头文件中,我们使用DECLARE_EVENT_TABLE()宏声明一个事件表。

  1. BEGIN_EVENT_TABLE(MyButton, wxFrame)
  2. EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
  3. END_EVENT_TABLE()

我们通过将每个事件映射到适当的成员函数来实现事件表。

使用Connect()的示例

我们将讨论移动事件。 移动事件保存有关移动更改事件的信息。 当我们将窗口移到新位置时,将生成一个移动事件。 表示移动事件的类为wxMoveEventwxEVT_MOVE是事件类型。

move.h

  1. #include <wx/wx.h>
  2. class Move : public wxFrame
  3. {
  4. public:
  5. Move(const wxString& title);
  6. void OnMove(wxMoveEvent & event);
  7. wxStaticText *st1;
  8. wxStaticText *st2;
  9. };

move.cpp

  1. #include "move.h"
  2. Move::Move(const wxString& title)
  3. : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
  4. {
  5. wxPanel *panel = new wxPanel(this, -1);
  6. st1 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 10));
  7. st2 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 30));
  8. Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
  9. Centre();
  10. }
  11. void Move::OnMove(wxMoveEvent& event)
  12. {
  13. wxPoint size = event.GetPosition();
  14. st1->SetLabel(wxString::Format(wxT("x: %d"), size.x ));
  15. st2->SetLabel(wxString::Format(wxT("y: %d"), size.y ));
  16. }

main.h

  1. #include <wx/wx.h>
  2. class MyApp : public wxApp
  3. {
  4. public:
  5. virtual bool OnInit();
  6. };

main.cpp

  1. #include "main.h"
  2. #include "move.h"
  3. IMPLEMENT_APP(MyApp)
  4. bool MyApp::OnInit()
  5. {
  6. Move *move = new Move(wxT("Move event"));
  7. move->Show(true);
  8. return true;
  9. }

该示例显示窗口的当前位置。

  1. Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));

在这里,我们将wxEVT_MOVE事件类型与OnMove()方法连接在一起。

  1. wxPoint size = event.GetPosition();

OnMove()方法中的事件参数是特定于特定事件的对象。 在我们的例子中,它是wxMoveEvent类的实例。 该对象保存有关事件的信息。 我们可以通过调用事件的GetPosition()方法找出当前位置。

wxWidgets 中的事件 - 图1

图:移动事件

事件传播

事件有两种类型:基本事件和命令事件。 它们的传播方式不同。 事件传播是事件从子小部件传播到父小部件和祖父小部件等的事件。基本事件不传播。 命令事件确实传播。 例如,wxCloseEvent是一个基本事件。 此事件传播到父窗口小部件没有任何意义。

默认情况下,在事件处理器中捕获的事件停止传播。 要继续传播,我们必须调用Skip()方法。

propagate.h

  1. #include <wx/wx.h>
  2. class Propagate : public wxFrame
  3. {
  4. public:
  5. Propagate(const wxString& title);
  6. void OnClick(wxCommandEvent& event);
  7. };
  8. class MyPanel : public wxPanel
  9. {
  10. public:
  11. MyPanel(wxFrame *frame, int id);
  12. void OnClick(wxCommandEvent& event);
  13. };
  14. class MyButton : wxButton
  15. {
  16. public:
  17. MyButton(MyPanel *panel, int id, const wxString &label);
  18. void OnClick(wxCommandEvent& event);
  19. };

propagate.cpp

  1. #include <iostream>
  2. #include "propagate.h"
  3. const int ID_BUTTON = 1;
  4. Propagate::Propagate(const wxString& title)
  5. : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
  6. {
  7. MyPanel *panel = new MyPanel(this, -1);
  8. new MyButton(panel, ID_BUTTON, wxT("Ok"));
  9. Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
  10. wxCommandEventHandler(Propagate::OnClick));
  11. Centre();
  12. }
  13. void Propagate::OnClick(wxCommandEvent& event)
  14. {
  15. std::cout << "event reached frame class" << std::endl;
  16. event.Skip();
  17. }
  18. MyPanel::MyPanel(wxFrame *frame, int id)
  19. : wxPanel(frame, id)
  20. {
  21. Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
  22. wxCommandEventHandler(MyPanel::OnClick));
  23. }
  24. void MyPanel::OnClick(wxCommandEvent& event)
  25. {
  26. std::cout << "event reached panel class" << std::endl;
  27. event.Skip();
  28. }
  29. MyButton::MyButton(MyPanel *mypanel, int id, const wxString& label)
  30. : wxButton(mypanel, id, label, wxPoint(15, 15))
  31. {
  32. Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
  33. wxCommandEventHandler(MyButton::OnClick));
  34. }
  35. void MyButton::OnClick(wxCommandEvent& event)
  36. {
  37. std::cout << "event reached button class" << std::endl;
  38. event.Skip();
  39. }

main.h

  1. #include <wx/wx.h>
  2. class MyApp : public wxApp
  3. {
  4. public:
  5. virtual bool OnInit();
  6. };

main.cpp

  1. #include "main.h"
  2. #include "propagate.h"
  3. IMPLEMENT_APP(MyApp)
  4. bool MyApp::OnInit()
  5. {
  6. Propagate *prop = new Propagate(wxT("Propagate"));
  7. prop->Show(true);
  8. return true;
  9. }

在我们的示例中,面板上有一个按钮。 面板放置在框架小部件中。 我们为所有小部件定义一个处理器。

  1. event reached button class
  2. event reached panel class
  3. event reached frame class

当我们点击按钮时,我们得到了这个。 事件从按钮传播到面板和框架。

尝试省略一些Skip()方法,看看会发生什么。

取消事件

有时我们需要停止处理事件。 为此,我们称方法Veto()

veto.h

  1. #include <wx/wx.h>
  2. class Veto : public wxFrame
  3. {
  4. public:
  5. Veto(const wxString& title);
  6. void OnClose(wxCloseEvent& event);
  7. };

veto.cpp

  1. #include "veto.h"
  2. Veto::Veto(const wxString& title)
  3. : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
  4. {
  5. Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(Veto::OnClose));
  6. Centre();
  7. }
  8. void Veto::OnClose(wxCloseEvent& event)
  9. {
  10. wxMessageDialog *dial = new wxMessageDialog(NULL,
  11. wxT("Are you sure to quit?"), wxT("Question"),
  12. wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
  13. int ret = dial->ShowModal();
  14. dial->Destroy();
  15. if (ret == wxID_YES) {
  16. Destroy();
  17. } else {
  18. event.Veto();
  19. }
  20. }

main.h

  1. #include <wx/wx.h>
  2. class MyApp : public wxApp
  3. {
  4. public:
  5. virtual bool OnInit();
  6. };

main.cpp

  1. #include "main.h"
  2. #include "veto.h"
  3. IMPLEMENT_APP(MyApp)
  4. bool MyApp::OnInit()
  5. {
  6. Veto *veto = new Veto(wxT("Veto"));
  7. veto->Show(true);
  8. return true;
  9. }

在我们的示例中,我们处理wxCloseEvent。 当我们单击标题栏上的 X 按钮,按 Alt + F4 或从系统菜单中选择关闭时,将称为此事件。 在许多应用中,如果要进行一些更改,我们希望防止意外关闭窗口。 为此,我们必须连接wxEVT_CLOSE_WINDOW事件类型。

  1. wxMessageDialog *dial = new wxMessageDialog(NULL,
  2. wxT("Are you sure to quit?"), wxT("Question"),
  3. wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);

在关闭事件期间,我们显示一个消息对话框。

  1. if (ret == wxID_YES) {
  2. Destroy();
  3. } else {
  4. event.Veto();
  5. }

根据返回值,我们销毁窗口或否决事件。 注意,要关闭窗口,我们必须调用Destroy()方法。 通过调用Close()方法,我们将陷入无尽的循环。

窗口标识符

窗口标识符是在事件系统中唯一确定窗口标识的整数。 有三种创建窗口 ID 的方法:

  • 让系统自动创建一个 ID。
  • 使用标准标识符。
  • 创建我们自己的 ID。

每个小部件都有一个 id 参数。 这是事件系统中的唯一编号。 如果我们使用多个小部件,则必须在它们之间进行区分。

  1. wxButton(parent, -1)
  2. wxButton(parent, wxID_ANY)

如果为 ID 参数提供 -1 或wxID_ANY,则让 wxWidgets 自动为我们创建一个 ID。 自动创建的 ID 始终为负,而用户指定的 ID 必须始终为正。 当我们不需要更改窗口小部件状态时,通常使用此选项。 例如,静态文本在应用的生命周期内将永远不会更改。 如果需要,我们仍然可以获取 ID。 有一种方法GetId(),它将为我们确定 ID。

应尽可能使用标准标识符。 标识符可以在某些平台上提供一些标准的图形或行为。

ident.h

  1. #include <wx/wx.h>
  2. class Ident : public wxFrame
  3. {
  4. public:
  5. Ident(const wxString& title);
  6. };

ident.cpp

  1. #include "ident.h"
  2. Ident::Ident(const wxString& title)
  3. : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(200, 150))
  4. {
  5. wxPanel *panel = new wxPanel(this, -1);
  6. wxGridSizer *grid = new wxGridSizer(2, 3);
  7. grid->Add(new wxButton(panel, wxID_CANCEL), 0, wxTOP | wxLEFT, 9);
  8. grid->Add(new wxButton(panel, wxID_DELETE), 0, wxTOP, 9);
  9. grid->Add(new wxButton(panel, wxID_SAVE), 0, wxLEFT, 9);
  10. grid->Add(new wxButton(panel, wxID_EXIT));
  11. grid->Add(new wxButton(panel, wxID_STOP), 0, wxLEFT, 9);
  12. grid->Add(new wxButton(panel, wxID_NEW));
  13. panel->SetSizer(grid);
  14. Centre();
  15. }

main.h

  1. #include <wx/wx.h>
  2. class MyApp : public wxApp
  3. {
  4. public:
  5. virtual bool OnInit();
  6. };

main.cpp

  1. #include "main.h"
  2. #include "ident.h"
  3. IMPLEMENT_APP(MyApp)
  4. bool MyApp::OnInit()
  5. {
  6. Ident *ident = new Ident(wxT("Identifiers"));
  7. ident->Show(true);
  8. return true;
  9. }

在我们的示例中,我们在按钮上使用标准标识符。 在 Linux 上,按钮带有小图标。

wxWidgets 中的事件 - 图2

图:标识符

在本章中,我们讨论了 wxWidgets 中的事件。