wxWidgets事件处理

http://blog.chinaunix.net/u/25096/article_65466.html
每一个wxEvtHandler的派生类,例如frame,button,menu以及document等,都会在其内部维护一个事件表,用来 告诉wxWidgets事件和事件处理过程的对应关系。所有继承自wxWindow的窗口类,以及应用程序类都是wxEvtHandler的派生类.
 
创建一个静态的事件表(编译期创建)需要以下几个步骤:
(1)定义一个直接或间接继承自wxEvtHandler的类;
(2)为每一个你想要处理的时间定义一个处理函数;
(3)在这个类中使用DECLARE_EVENT_TABLE声明事件表;
(4)在.cpp文件中使用BEGIN_EVENT_TABLE和END_EVENT_TABLE实现一个事件表
(5)在事件表的实现中增加事件宏,来实现从事件到事件处理过程的映射.
 
事件表:
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
   EVT_MENU(wxID_EXIT, MyFrame::OnExit)
   EVT_SIZE(MyFrame::OnSize)
   EVT_BUTTON(BUTTON1, MyFrame::OnButton1)
END_EVENT_TABLE()
宏 BEGIN_EVENT_TABLE 声明wxFrame和它的子类MyFrame拥有这个事件表。
处理事件的成员函数不能是虚拟的。实际上事件处理器将忽略虚拟声明。事件处理函数有相同的形式:返回类型为void并且接受一个事件对象做为参 数。这个参数的类型与具体的事件相关。如简单控件(如按钮、菜单)的事件命令处理函数的参数都是wxCommandEvent类型;而size事件为 wxSizeEvent类型。
EVT_SIZE事件映射宏不需要标示参数,因为这个事件只会被产生这个事件的控件所处理。
 
事件的传输过程:
当接收到一个事件时,wxWindows首先调用窗口上产生事件的对象上的 wxEventHandler的ProcssEvent处理器。wxWindow(和其它的窗口类)继承自wxEventHander。 ProcessEvent查找事件表里的每一个事件并且调用零或多个事件处理器函数。
 
下面是处理一个事件的步骤:
1. 当对象关闭时(包括wxEvtHandler的SetEvtHandle)函数跳转到第六步。
2. 如果对象是一个wxWindow对象,ProcessEvent在窗口的wxValidator上递归调用。  如果返回真函数退出。
3. SearchEventTable是事件处理器调用的函数。当它失败时,开始在基类上尝试直到  没有更多的事件表或者发现了一个合适的函数时这个函数退出。被发现的函数开始执行。
4. 查找过程应用于整个事件处理器链,当应用成功时(表示事件处理完毕)函数退出。
5. 当对象是一个wxWindow对象时,并且事件为wxCommandEvent类型时,  ProcessEvent向上递归应用于父窗口的事件处理器。当它返回真,函数退出。  这可以让一个按钮的父亲来处理按钮的点击而不是让按钮自己来处理。
6. 最后ProcessEvent 在wxApp对象上调用。
 
 
 

当点击了确定按钮后, 一个新的wxCommandEvent事件被创建,其中包含了标示符BUTTON_ID_OK和 事件类型wxEVT_COMMAND_BUTTON_CLICKED:然后这个按钮的事件表开始通过 wxEvtHandler::ProcessEvent函数进行匹配,事件表中的每一个条目都会去尝试匹配,然后是其父类wxControl的类事件表, 然后是wxWidgets的。如果都没匹配到,wxWidgets会搜索其父窗口的类事件表,然后就会找到一条匹配条目:

EVT BUTTON(BUTTON_ID_OK,TextFrame::OnButtonOK)

于是调用了TextFrame::OnButtonOK函数
注意:只有Command事件(指的是哪些直接或者间接的继承自wxCommandEvent的事件)才会被递归的应用到其父窗口的事件表。

不会传递的事件如下:
wxActivate, wxCloseEvent, wxEraseEvent, wxFocusEvent,wxKeyEvent, wxIdleEvent, wxInitDialogEvent, wxJoystickEvent, wxMenuEvent,wxMouseEvent, wxMoveEvent, wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent,wxScrollWinEvent, 和wxSysColourChangedEvent,这些事件不会传给事件源控件的父窗口。
 
过滤事件:
可以修改本地原生控件的默认行为。如过滤某些按键事件,如只允许数字输入。这时候应该继 承一个wxTextCtrl新的类,然后在其事件表中使用EVT_KEY_DOWN事件映射宏。而ProcessEvent在发现一个可以处理事件的函数 后退出。这表示当你的类对一个事件起反应时,下层的类不会得到这个事件。有时我们不希望这样。这个问题可以根据基类的事件类型用wxEvent类的 Skip方法来解决,使事件处理器的查找继续进行。
 
void MyTextCtrl::OnChar(wxKeyEvent& event)
{
if ( wxIsalpha( event.KeyCode() ) )
{
   event.Skip();
}
else
{
   wxBell();
}
}

挂载事件表:
并不一定要实现继承自某个类的新类,才可以改变它的事件表。对于继承自 wxWindows的类来说,有另一种方法!可以通过实现一个新的直接继承自wxEvtHandler的新类,然后定义新类的事件表,然后使用 wxWindow::PushEventHandler函数将这个事件表压入到某个窗口类的事件表栈中。最后压入的那个事件表在事件匹配过程中将会被最先 匹配,如果在其中没有匹配到对应事件处理过程,那么栈中以前的事件表仍被匹配。还可以用wxWindow::PopEventHandler函数来弹出最 顶层的事件表,如果你给wxWindow::PopEventHandler函数传递True参数,那么这个弹出的事件表将被删除。
 
考虑下面的问题:每个菜单命令都必须被记录。一个解决方案是创建一个在每个命令事件处理函数中调用的函数。这种方法带来的问题是使维护变得十分困难。当添加一个新的菜单并且没有调用这个函数时,这个菜单命令将不被记录。

解决这个问题是去创建一个新的事件处理器并添加到一个wxWindow类。要完成它需要创建一个从wxEvtHandler派生的新类。在新类中处理事件与一个正常的窗口是相同的。

 LogEventHandler.h -LogEventHandler的定义
#ifndef _LogEventHandler_H
#define _LogEventHandler_H
class LogEventHandler : public wxEvtHandler
{
   public:
    LogEventHandler() : wxEvtHandler()
    {
    }
    virtual ~LogEventHandler()
    {
    }
  protected:
    DECLARE_EVENT_TABLE()
    void OnMenu(wxMenuEvent &event);
  private:
};
#endif // _LogEventHandler_H
 LogEventHandler.cpp - LogEventHandler的实现
// For compilers that supports precompilation , includes "wx/wx.h"
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif
#include "LogEventHandler.h"
void LogEventHandler::OnMenu(wxMenuEvent &event)
{
  wxLogMessage("Someone selected a menu item");
  event.Skip();
}
BEGIN_EVENT_TABLE(LogEventHandler, wxEvtHandler)
    EVT_MENU_RANGE(-1, -1, LogEventHandler::OnMenu)
END_EVENT_TABLE()
 
在宏EVT_MENU_RANGE里,所有的菜单事件都可以被处理。前两个参数用来指定要处理的菜单的ID范围。-1表示处理所有的菜单项。不要忘记在事件上调用Skip,否则不会有任何事件传递到类wxWindow的事件处理器。
下一步是把新的事件处理器压入处理器栈。这是使用wxWindow的PushEventHandler来完成的。要从栈上移除事件处理器使用PopEventHandler。

PushEventHandler(new LogEventHandler());

动态事件处理:
动态映射方法可以使你更精确的控制事件表的细节,你甚至可以单独的将事件表中的某一个条目在运行期打开或关闭。而PushEventHandler和PopEventHandler的方法只能针对整个事件表进行处理。动态事件处理还允许你在不同的类之间共享事件函数。
 
和动态事件相关的函数有: wxEvtHandler::ConnectwxEvtHandler::Disconnect
大多数情况下不需要wxEvtHandler::Disconnect函数,这个函数将在窗体类被释放时自动被调用。
 
class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title);
void OnQuit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
private :
};
没有使用 DECLARE_EVENT_TABLE来声明一个事件。
这时候需要OnInit函数中增加下面的代码:

frame>Connect( wxID_EXIT,
wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(MyFrame::OnQuit) );
frame>Connect( wxID_ABOUT,
wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(MyFrame::OnAbout) );
 
传递给Connect函数的三个参数分别为菜单标示符,事件标示符和事件处理函数指针。
这里wxEVT_COMMAND_MENU_SELECTED不同于EVT_MENU,实际上EVT_MENU内部也使用了
wxEVT_COMMAND_MENU_SELECTED.EVT_MENU其实也自动包含了用于对事件处理指针类型强制转换的宏 wxCommandEventHandler().一般来说,如果事件处理函数的参数类型是wxXYZEvent,那么其处理函数的类型就应该用 wxXYZEventHandler宏进行强制转换。
 
如果希望终止上面的事件处理,应该是:

frame>Disconnect( wxID EXIT,
wxEVT COMMAND MENU SELECTED,
wxCommandEventHandler(MyFrame::OnQuit) );
frame>Disconnect( wxID ABOUT,
wxEVT COMMAND MENU SELECTED,
wxCommandEventFunction(MyFrame::OnAbout) );
 
窗口标示符:
窗口标示符是在事件系统中用来唯一确定窗口的整数。在整个应用程序的范围内,窗口标示 符不必一定是唯一的,而只要在某个固定的上下文(比如在一个frame窗口和它的所有字窗口)内是唯一的就可以了。如果在窗口的构造函数中使用 wxID_ANY作为其标示符 ,则意味着你希望wxWidgets自动为你生成一个标示符。wxWidgets自动创建的表示符是总是一个负号。所以永远不会和用户定义的标示符重复, 用户定义的标示符只能是正整数。

自定义事件:
(1)从一个合适的事件类派生一个你自己的时间类,声明动态类型信息并且实现 一个Clone函数,按照你自己的意愿增加新的数据成员和函数成员,如果你希望这个时间在窗口继承关系之间传递,你应该使用wxCommandEvent 派生类,如果你希望这个事件的处理函数可以调用Veto,你应该使用wxNotifyEvent的派生类。
(2)为这个事件类的处理函数定义类型。
(3)定义一个你的事件类支持的事件类型表。这个表应该定义在你的头文件中。用BEGIN_DECLARE_EVENT_TYPES()宏和
END_DECLARE_EVENT_TABEL(name,integer)格式的宏。然后在你的.cpp文件中使用DEFINE_EVENT_TYPE(name)来实现这个
事件类。
(4)为每个你的事件类支持的事件定义一个事件映射宏。
 
附代码(主要包含了定义过滤时间,动态把时间关联到一个按钮上,其他和以前的框架相同)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值