【wxWidgets 教程】事件篇Ⅱ(四)

文章详细介绍了wxWidgets中用于事件处理的Bind函数的两个重载形式,包括参数解析和使用示例,强调了ID范围和用户数据的作用。同时提到了Connect函数的不推荐使用和事件表的宏定义,以及事件处理器的分类和参数差异。最后,文章指出事件处理器应与特定事件类型匹配,并提供了事件处理的相关注意事项。
摘要由CSDN通过智能技术生成

参考文档:
https://docs.wxwidgets.org/3.2/overview_events.html
https://docs.wxwidgets.org/3.2/classwx_evt_handler.html

一、Bind 函数

● 参数介绍

目前在3.2版本,Bind 函数一共有两个重载:

// 重载1
template<typename EventTag, typename Functor>
void Bind (const EventTag &eventType, Functor functor,
           int id = wxID_ANY, int lastId = wxID_ANY,
           wxObject *userData = NULL);

// 重载2
template<typename EventTag, typename Class, typename EventArg, typename EventHandler>
void Bind (const EventTag &eventType, void(Class::*method)(EventArg &),
           EventHandler *handler, int id = wxID_ANY, int lastId = wxID_ANY,
           wxObject *userData = NULL);
  • 先来说一下第一个重载函数,它的第一个参数是要与此事件处理函数关联的事件类型实例,这些实例的类型既可以是预定义的 wxEventType 类型,也可以自定义事件类型。至于如何自定义类型,我们后面再介绍。

    第二个参数就是要绑定的函数,这里很明显,它通常用于绑定一个非成员函数(例如普通函数、lambda表达式或者任意的函数对象),因为后面也没有提供能指定类对象的传参入口。事件处理函数的参数是第一个参数 eventType 决定的,例如如果 eventType 传入的是 wxEVT_BUTTON,那么事件处理函数的参数类型就要是 wxCommandEvent

    第三和第四个参数指的是与事件处理函数绑定的标识符范围的开始ID和结束ID,说人话就是指定一个ID的范围,只要这个范围内的ID对应的控件在当前调用的 Bind 函数对应的窗口中,那么这些控件就会在触发 eventType 事件时调用这个事件处理函数。理解不了的可以反复看多两遍,若还是理解不了……上代码:

    // MyFrame的构造函数
    explicit MyFrame::MyFrame(const wxString &title)
     : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
    {
       // 创建一个面板
       auto *panel = new wxPanel(this, wxID_ANY);
    
       // 创建按钮
       auto *button = new wxButton(panel, 33, L"按我!",
                                   wxPoint(60, 40), wxSize(100, 30));
       auto *button2 = new wxButton(panel, 25, L"别按!",
                                    wxPoint(220, 40), wxSize(100, 30));
    
       // 绑定按钮点击事件
       Bind(wxEVT_BUTTON, [](wxCommandEvent &event) {
           if (event.GetId() == 25) {
               wxMessageBox(wxT("都叫你别按!"), wxT("别按"), wxOK | wxICON_INFORMATION);
           }
           else {
               wxMessageBox(wxT("你好!"), wxT("按我"), wxOK | wxICON_INFORMATION);
           }
       }, 20, 40);
    }
    

    代码中很明显,Bind 函数是 MyFrame 的,但它却能直接处理两个按钮的按下事件,因为这两个按钮的ID都在“[20, 40]”这个闭区间中,且按钮在 MyFrame 中(什么?按钮属于的父对象不是this?我可没说过要直属,间接的也行)。

    第五个参数,是一个供用户在绑定是传入的动态数据。这个数据类型必须是继承于 wxObject 的,指针一旦传入,这个指针将归wxWidgets所有,即在事件处理程序断开连接或程序终止时,它将被销毁。出于好奇我跟踪了一下代码,wxWidgets是真的把 userDatadelete 掉了。在事件处理函数中,可以调用 wxEvent::GetEventUserData() 函数检索此指针。切记,wxEvent::GetEventUserData() 是一个成员函数,不要当作静态函数直接照搬来调用了。

  • 第二个重载函数的参数很大一部分是相同的,主要区别就在于它多了一个 handler 参数,这个参数是用来指定事件处理函数的所属对象。直接上代码(也不解释了,就是这么个意思):

    // MyFrame的构造函数
    explicit MyFrame::MyFrame(const wxString &title)
      : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
    {
        // 创建一个面板
        auto *panel = new wxPanel(this, wxID_ANY);
    
        // 创建按钮
        auto *button = new wxButton(panel, 33, L"按我!",
                                    wxPoint(60, 40), wxSize(100, 30));
        auto *button2 = new wxButton(panel, 25, L"别按!",
                                     wxPoint(220, 40), wxSize(100, 30));
    
        // 绑定按钮点击事件
        Bind(wxEVT_BUTTON, &MyFrame::OnButtonClicked, this, 20, 40);
    }
    
    // MyFrame的成员函数
    void MyFrame::OnButtonClicked(wxCommandEvent &event)
    {
        if (event.GetId() == 25) {
            wxMessageBox(wxT("都叫你别按!"), wxT("别按"), wxOK | wxICON_INFORMATION);
        }
        else {
            wxMessageBox(wxT("你好!"), wxT("按我"), wxOK | wxICON_INFORMATION);
        }
    }
    

● 注意事项

  1. Bind 函数默认使用的是 C++ RTTI 机制(除非你禁用了C++的RTTI后再进行wxWidgets的编译,此时使用的是wxWidgets内建的RTTI,总之无论怎样,都要用到RTTI机制),抛开技术层面直白点说就是相对有点消耗资源,当然它的资源消耗比Qt的信号槽机制要少。如果想要追求极致性能,可使用事件表。

  2. BindidlastId 这两个参数并不总是有效,这取决于事件类型,比如对于 wxEVT_MOTION 事件,它们就失效了。

  3. Bind 就会有 Unbind,不过需要注意的是,在设置ID范围的参数生效的前提下,如果 Bind 自行指定了ID范围,那么 Unbind 也必须自行指定范围,使用默认ID值 wxID_ANY 它是无法解绑自定义ID范围的事件的。以下贴出了 Unbind 的函数声明:

    // 重载1
    template<typename EventTag, typename Functor>
    bool Unbind (const EventTag &eventType, Functor functor,
                 int id = wxID_ANY, int lastId = wxID_ANY,
                 wxObject *userData = NULL);
    
    // 重载2
    template<typename EventTag, typename Class, typename EventArg, typename EventHandler>
    bool Unbind (const EventTag &eventType, void(Class::*method)(EventArg &),
                 EventHandler *handler, int id = wxID_ANY, int lastId = wxID_ANY,
                 wxObject *userData = NULL);
    

    至于最后的那个 userData 参数,保持 NULL 就行,有绑定用户数据的话会自动释放的。

二、Connect 函数

此函数的作用是将事件处理函数与ID和事件类型动态连接。这个函数我属实没怎么用过,没法详细介绍,官方也不推荐使用,我直接复制官方的原话吧:

Notice that Bind() provides a more flexible and safer way to do the same thing as Connect(), please use it in any new code – while Connect() is not formally deprecated due to its existing widespread usage, it has no advantages compared to Bind() and has a number of drawbacks, including:

  • Less compile-time safety.
  • Unintuitive parameter order.
  • Limited to use with the methods of the classes publicly inheriting from wxEvtHandler.

This is an alternative to the use of static event tables. It is more flexible as it allows connecting events generated by some object to an event handler defined in a different object of a different class (which is impossible to do directly with the event tables – the events can be only handled in another object if they are propagated upwards to it). Do make sure to specify the correct eventSink when connecting to an event of a different object.

翻译过来的意思就是:如果写新的代码,建议使用 Bind() 函数来处理事件,而不是 Connect() 函数。因为 Bind() 更灵活、更安全,而且比 Connect() 更好用。如果使用 Connect() 函数,可能会遇到一些问题,比如编译时的安全性可能会降低,参数顺序可能也不是很直观。而且,Bind() 函数可以将一个对象生成的事件连接到不同类的另一个对象中定义的事件处理程序,这个在 Connect() 函数里面是做不到的。不过,在连接到不同对象的事件时,记得要指定正确的 eventSink(至于 eventSink 是什么,大家可以自行去查看 Connect 函数的声明)。

如果有哪位大神能给我提供 Connect 函数的详细介绍,感激不尽~~~

三、事件表

● 基本宏介绍

首先来看看与事件表有关的几个重要宏:

#define wxDECLARE_EVENT_TABLE ()

#define wxBEGIN_EVENT_TABLE	(theClass, baseClass)		

#define wxEND_EVENT_TABLE ()	
  • wxDECLARE_EVENT_TABLE:此宏用于在类定义中声明一个事件表,事件表是一个用于存储事件处理器的数据结构,它用于在事件发生时调用相应的处理函数。此宏最好放到类声明的最末尾处,因为它会进行类的成员访问限制(就是里面会添加 privateprotected 等限制关键字)。
  • wxBEGIN_EVENT_TABLE(theClass, baseClass):此宏用于实际定义并初始化事件表。theClass 是当前类的名称,而 baseClass 是当前类所继承的基类。这个宏通常在类的实现文件中使用,它标志着事件表的开始。在此宏之后,您可以使用其他宏(如 EVT_BUTTONEVT_MENU 等)来定义事件处理器和它们处理的事件。
  • wxEND_EVENT_TABLE():此宏标志着事件表定义的结束。在使用wxBEGIN_EVENT_TABLE宏之后定义了所有事件处理器之后,必须使用此宏来关闭事件表。这个宏通常紧跟在事件处理器定义的最后一个宏之后。

以下给出示例代码:

// header file (.h)
class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString &title);

private:
    void OnButtonClicked(wxCommandEvent &event);

wxDECLARE_EVENT_TABLE();  // 记得最好放在这个位置,即类声明的末尾
};


// implementation file (.cpp)
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_BUTTON(BUTTON_ID, MyFrame::OnButtonClicked)
wxEND_EVENT_TABLE()

MyFrame::MyFrame(const wxString &title)
  : wxFrame(NULL, wxID_ANY, title)
{
    // ...
}

void MyFrame::OnButtonClicked(wxCommandEvent &event)
{
    // ...
}

示例中,我们使用了 wxDECLARE_EVENT_TABLE() 在类定义中声明事件表,然后使用 wxBEGIN_EVENT_TABLEwxEND_EVENT_TABLE 在实现文件中定义和初始化事件表。事件处理器 OnButtonClicked 与相应的事件(此处为按钮点击)相关联。

● 事件处理器

事件处理器是处理用户界面(UI)或系统事件的函数。在wxWidgets中,事件处理器用于响应各种类型的事件,例如用户点击按钮、选择菜单项、键入文本等。事件处理器可以根据它们处理的事件类型划分为六类(自己划分的,有错误请大家及时指出)。以下是一些主要类别及其示例:

1. 鼠标事件。处理鼠标操作的事件,如点击、双击、移动和拖拽等。例如:

  • EVT_LEFT_DOWN:左键按下事件
  • EVT_RIGHT_UP:右键弹起事件
  • EVT_MOTION:鼠标移动事件

2. 键盘事件。处理与键盘输入相关的事件。例如:

  • EVT_KEY_DOWN:按键按下事件
  • EVT_KEY_UP:按键弹起事件
  • EVT_CHAR:字符输入事件

3. 窗口事件。处理与窗口相关的事件,如窗口创建、销毁、大小调整等。例如:

  • EVT_SIZE:窗口大小调整事件
  • EVT_CLOSE:窗口关闭事件
  • EVT_PAINT:窗口重绘事件

4. 控件事件。处理与界面控件相关的事件,如按钮、文本框、菜单项等。例如:

  • EVT_BUTTON:按钮点击事件
  • EVT_TEXT:文本框内容更改事件
  • EVT_MENU:菜单项选择事件

5. 自定义事件。处理用户自定义事件,可以通过创建自定义事件类并为其分配事件处理器来实现。例如:

  • EVT_CUSTOM:用户自定义事件

6. 特殊事件。不属于以上几类的事件,我都归集到这里。例如:

  • EVT_IDLE:处理空闲事件。当应用程序没有其他事件处理时,此事件处理器将被调用。
  • EVT_TIMER:处理定时器事件。在指定时间间隔内,定时器事件将触发相应的处理器。

● 事件处理器的参数

每一类型的事件处理器所能接受的参数个数有可能不同,以下是一个例子:

wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(wxID_EXIT, MyFrame::OnExit)
    EVT_MENU(DO_TEST, MyFrame::DoTest)
    EVT_SIZE(MyFrame::OnSize)
    EVT_BUTTON(BUTTON1, MyFrame::OnButton1)
wxEND_EVENT_TABLE()

接受两个参数的事件处理器宏(事件ID和处理器函数)通常用于处理与用户界面控件相关的事件,例如按钮点击、菜单项选择等。这是因为这些事件通常会涉及多个相同类型的控件,例如一个窗口中可能包含多个按钮。通过使用ID参数,我们可以为同一事件类型的不同实例(例如不同的按钮)指定单独的处理器。例如:

  • EVT_BUTTON(BUTTON1, MyFrame::OnButton1)
  • EVT_BUTTON(BUTTON2, MyFrame::OnButton2)
  • EVT_MENU(wxID_EXIT, MyFrame::OnExit)
  • EVT_LIST_ITEM_SELECTED(LIST_CTRL1, MyFrame::OnItemSelected)

只接受一个参数(事件处理器函数)的事件处理器宏通常用于处理与窗口和系统相关的事件,例如窗口大小调整、激活等。对于这些事件,通常不需要区分不同的实例,因为它们只与窗口或系统状态有关,而与具体的控件无关。例如:

  • EVT_SIZE(MyFrame::OnSize)
  • EVT_CLOSE(MyFrame::OnClose)
  • EVT_PAINT(MyFrame::OnPaint)

总结一下:接受两个参数的事件处理器宏主要用于与控件相关的事件,因为这些事件可能需要区分不同的控件实例;只接受一个参数的事件处理器宏通常用于与窗口和系统相关的事件,因为这些事件与具体的控件无关。

还有一点我想提醒一下的,能接受ID参数的事件处理器宏,它对应的事件在用 Bind 进行事件绑定时,设置ID范围的那两个参数是生效的。

● 注意事项

  1. 一个事件处理器通常只能处理一个特定类型的事件。例如,按钮点击事件处理器不能处理文本框内容更改事件。
  2. 事件处理器的签名必须与预期的事件类型匹配。例如,按钮点击事件处理器的声明应为 void OnButtonClick(wxCommandEvent& event)
  3. 事件处理器的访问修饰符通常应为 privateprotected ,以防止其他类误用它们。其实这点我们也没必要过多关心,wxWidgets已经为我们做好了相应的工作。

事件篇Ⅱ 至此完毕,欢迎大家指正!还请大家点点赞,给我点动力~~

上一篇:【wxWidgets 教程】事件篇Ⅰ(三)
下一篇:【wxWidgets 教程】事件篇Ⅲ(五)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao_Ley

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值