目录
- 动态事件处理
- 如何处理事件
- 事件如何向上传播
- 事件处理程序链
动态事件处理
void MyFrameHandler::OnFrameExit(wxCommandEvent&)
{
// Do something useful.
}
MyFrameHandler myFrameHandler;
MyFrame::MyFrame()
{
Bind(wxEVT_MENU, &MyFrameHandler::OnFrameExit,
&myFrameHandler, wxID_EXIT);
}
注意,MyFrameHandler不需要从wxEvtHandler派生。但请记住,myFrameHandler的生存期必须大于MyFrame对象的生存期,或者至少需要在销毁之前解除绑定。
要使用普通函数或静态方法作为事件处理程序,可以编写如下内容:
void HandleExit(wxCommandEvent&)
{
// Do something useful
}
MyFrame::MyFrame()
{
Bind(wxEVT_MENU, &HandleExit, wxID_EXIT);
}
最后,您可以绑定到任意函子并将其用作事件处理程序:
struct MyFunctor
{
void operator()(wxCommandEvent&)
{
// Do something useful
}
};
MyFunctor myFunctor;
MyFrame::MyFrame()
{
Bind(wxEVT_MENU, myFunctor, wxID_EXIT);
}
在C++11中,可以直接使用lambda表达式,而不必定义单独的函子类:
MyFrame::MyFrame()
{
Bind(wxEVT_MENU,
[](wxCommandEvent&) {
// Do something useful
},
wxID_EXIT);
}
泛型函子的另一个常见示例是boost::function<>,或者,从C++11开始,std::function<>:
#if wxCHECK_CXX_STD(201103L)
using namespace std;
using namespace std::placeholders;
#else // Pre C++11 compiler
using namespace boost;
#endif
void MyHandler::OnExit(wxCommandEvent&)
{
// Do something useful
}
MyHandler myHandler;
MyFrame::MyFrame()
{
function<void (wxCommandEvent& )> exitHandler(bind(&MyHandler::OnExit, &myHandler, _1));
Bind(wxEVT_MENU, exitHandler, wxID_EXIT);
}
借助bind<>(),甚至可以使用没有正确签名的方法或函数:
void MyHandler::OnExit(int exitCode, wxCommandEvent&, wxString goodByeMessage)
{
// Do something useful
}
MyHandler myHandler;
MyFrame::MyFrame()
{
function<void (wxCommandEvent&)> exitHandler(
bind(&MyHandler::OnExit, &myHandler, EXIT_FAILURE, _1, "Bye"));
Bind(wxEVT_MENU, exitHandler, wxID_EXIT);
}
总之,使用Bind<>()需要稍微更多的类型,但比使用静态事件表更灵活,因此在需要额外的功能时,不要犹豫使用它。另一方面,在不需要这种额外灵活性的简单情况下,事件表仍然非常好。
如何处理事件
当从窗口系统接收到事件时,wxWidgets将调用wxEvtHandler::ProcessEvent(),该对象属于生成事件的窗口。
ProcessEvent()搜索事件表的正常顺序如下,一旦找到处理程序,事件处理就会停止(除非处理程序调用wxEvent::Skip(),在这种情况下,它不算处理了事件,搜索会继续):
0.在发生任何其他事情之前,调用wxApp::FilterEvent()。如果它返回除-1以外的任何值(默认值),则事件处理将立即停止。
1.如果对象是wxWindow并具有关联的验证器,则wxValidator有机会处理该事件。
2.如果通过调用wxEvtHandler::SetEvtHandlerEnabled()禁用了此事件处理程序,则跳过接下来的两个步骤,并在步骤(5)继续执行事件处理程序。
3.查询动态绑定事件处理程序的列表,即调用Bind<>()的事件处理程序。请注意,这是在检查静态事件表条目之前完成的,因此,如果动态和静态事件处理程序都匹配同一事件,则除非在动态事件处理程序中调用wxEvent::Skip(),否则永远不会使用静态事件。还请注意,在程序运行时,动态绑定的处理程序按照其注册的相反顺序进行搜索,即,稍后绑定的处理比之前绑定的处理优先。」
4.检查包含使用该类及其基类中的事件表宏定义的所有处理程序的事件表。事件表中的搜索遵循源代码中事件宏出现的顺序,即较早出现的条目优先于较晚出现的条目。注意,这意味着在基类中定义的任何事件处理程序都将在这一步执行。
5.事件被传递到事件处理程序链中的下一个事件处理程序(如果有的话),即对其执行步骤(1)到(4)。
6.如果对象是wxWindow,并且事件被设置为传播(默认情况下,只有wxCommandEvent派生的事件被设置成传播),则处理从父窗口的步骤(1)(不包括步骤(7))重新开始。如果此对象不是窗口,但存在下一个处理程序,则如果它是窗口,则将事件传递给其父对象。这确保了在通常情况下(可能是几个)非窗口事件处理程序被推到窗口顶部时,事件最终到达窗口父级。
7.最后,即,如果事件仍然没有处理,wxApp对象本身(从wxEvtHandler派生)将获得最后一次处理它的机会。
请密切注意第6步!人们常常忽视或被wxWidgets事件处理系统的这一强大功能所迷惑。事件在窗口层次结构上传播。
此外,请注意,作为wxWidgets文档视图框架一部分的窗口的事件处理中还有其他步骤,即wxDocParentFrame、wxDocChildFrame及其MDI等价物wxDocMDIParentFrame和wxDocMDIChildFrame。
- 父框架类修改上面的步骤(2),将它们接收到的事件首先发送到wxDocManager对象。
- 然后,该对象将事件发送到当前视图,视图本身允许其关联文档首先处理该事件。
- 子框架类将事件直接发送到关联视图,该视图仍将其转发到其文档对象。
请注意,为了避免记住事件在文档视图框架中处理的确切顺序,最简单也是推荐的解决方案是仅在视图类级别处理事件,而不是在文档或文档管理器类中处理事件
事件如何向上传播
如上所述,从wxCommandEvent派生的类的事件如果没有在该窗口本身中处理,则默认情况下会传播到父窗口。但是,尽管默认情况下只有命令事件是这样传播的,但也可以传播其他事件,因为事件处理代码使用wxEvent::ShouldPropagate()来检查是否应该传播事件。也可以只传播有限次数的事件,直到它被处理(或到达顶级父窗口)。
最后,还有一个额外的复杂性(事实上,这大大简化了wxWidgets程序员的生活):当将命令事件传播到父窗口时,事件传播在到达父对话框时停止(如果有的话)。这意味着当弹出模式对话框时,您不会冒险从对话框控件中获取意外事件(对话框本身可能不会处理这些事件,因为它不关心这些事件)。然而,事件确实会传播到帧之外。这种选择的理由是,在一个典型的应用程序中只有几个框架,程序员很好地理解它们的父子关系,而如果不是不可能的话,可能很难追踪复杂程序中可能弹出的所有对话框(请记住,有些是由wxWidget自动创建的)。如果出于某种原因需要指定不同的行为,则可以显式使用wxWindow::SetExtraStyle(wxWS_EX_BLOCK_EVENTS)来防止事件传播到给定窗口之外,或者在默认情况下为打开该标志的对话框取消设置该标志。
通常,将窗口作为窗口处理的事件(大小、运动、绘画、鼠标、键盘等)仅发送到窗口。具有更高级别含义或由窗口本身生成的事件(按钮单击、菜单选择、树展开等)是命令事件,并发送给父级,以查看其是否对事件感兴趣。更准确地说,如上所述,所有不是从wxCommandEvent派生的事件类(参见wxEvent继承映射)都不会向上传播。
在某些情况下,程序员可能希望在父窗口中获得一定数量的系统事件,例如,发送到对话框中的本地控件但未被其使用的所有关键事件。在这种情况下,必须编写一个特殊的事件处理程序,它将重写ProcessEvent(),以便将所有事件(或它们的任何选择)传递给父窗口。
事件处理程序链
事件传播算法的步骤5检查事件处理程序链中的下一个处理程序。此链可以使用wxEvtHandler::SetNextHandler()形成:
(参考图像,如果调用了A->ProcessEvent,但它不处理该事件,则将调用B->ProcessEvent等…)。
此外,在wxWindow的情况下,可以使用wxWindow::PushEventHandler()构建堆栈(使用wxEvtHandler双链接列表实现):
(参考图,如果调用了W->ProcessEvent,它将立即调用A->ProcessEvent;如果A或B都不处理该事件,则使用wxWindow本身——即,在测试所有推送的事件处理程序之后,wxWindow的动态绑定事件处理程序和静态事件表项被视为最后一种可能)。
默认情况下,链为空,即没有下一个处理程序。