Q.C人生

君子博学而日参省乎己,则知明而行无过矣

导航

wxWidgets学习笔记(4) 事件驱动(III)

Posted on 2010-12-19 20:08  ChanghW  阅读(4401)  评论(0编辑  收藏  举报

2.4用户自定义事件

如果出于某种问题的考虑,你可能需要你自己的类具备一些特殊的行为或事件,比如我制作了一个类似Word的文字编辑程序,其中有一个选择字体的自定义控件,我希望在选择完字体后,系统替我修改我选中文字的字体。这是就需要产生一个SELECTED_CHANGED事件在这一事件中进行一些后处理工作。那么这就用到了自定义事件。自定义事件有两种,首先是基于已有的事件类。其次是自己建立一个事件类,我们先看看第一种比较简单的情况如何实现。

2.4.1 利用已有事件类建立自定义事件

      wxWidgets为我们提供了大量的基础事件类,如:wxCommandEventwxNotifyEventwxThreadEventwxScrollEvent等等。我们可以直接拿来使用,在使用前我们需要了解两个宏,wxDECLARE_EVENTwxDEFINE_EVENT,这两个宏都是为我们定义自己的事件类型服务的。正常情况下,我们只需要使用wxDEFINE_EVENT,如果我们需要在.h文件中使用我们定义的事件,那么在.h文件中需要引入wxDECLARE_EVENT.

如果我们在实现文件中定义 wxDEFINE_EVENTwxEVT_SELECTED_CHANGED, wxCommandEvent)那么就相当于定义了一个wxCommandEvent型的变量,这个宏展开后的结果是:

const wxEventTypeTag< wxCommandEvent > wxEVT_SELECTED_CHANGED ( wxNewEventType() );

 

那么wxDECLARE_EVENT这个宏又是什么呢?我们也把它展开

extern const expdecl wxEventTypeTag< wxCommandEvent > wxEVT_SELECTED_CHANGED;

 

我们看到这仅仅是一个声明变量。

完成了这个声明以后,我们需要在引发事件的地方加入如下的语句

wxCommandEvent event(wxEVT_SELECTED_CHANGED, GetId());

//可以在此进行event一些变量的初始化工作,比如设置被选中的字体等

 event.SetEventObject(this);

ProcessWindowEvent(event);  

首先我们声明一个事件类的实例,然后进行一些事件实例的初始化工作,最后将调用者写入实例对象并引发事件

有好几种方式能够引发事件,上面例子中是最简单的一种他实际上是调用windowsBase类中的事件句柄,然后引发事件,相当于这个语句   GetEventHandler()->ProcessEvent(event);常用的引发事件的方式有两种一个是ProcessWindowEvent另一个是QueueEvent,他们的行为基本一致,区别有两点:

l  ProcessWindowEvent 是同步处理,处理函数运行完成后才能返回;而后者是异步处理,提交后立即返回,事件具体在何时运行并不确定。

 

l  如果我们的事件类有引用类型的参数,那么调用QueueEvent时必须进行deep copy,否则在事件返回后引用内容有可能不可用,

注:通过分析源代码我们可以发现,使用Bind()方法会更有效率,因为他可以防止系统去查看静态事件表并进行迭代处理。

 

好了,至此我们已经引发了一个事件,剩下的就是使用2.3的方法去处理事件了。可是我们有的时候还是想用静态事件表处理那应该这么办呢?在.h中加入面这段代码就OK了。

#define wxFontSelectEdChangeFunction(func) (&func)

 

#define EVT_SELECED_CHANGED(id, func) \

    wx__DECLARE_EVT1(wxEVT_SELECTED_CHANGED, id, wxFontSelectEdChangeFunction (func))

首先声明一个函数指针,定义处理函数的签名,然后定义一个宏帮助客户端向事件表插入项目。那么在客户端我们就可以使用2.2的方法进行调用了。

BEGIN_EVENT_TABLE(MyFrame, wxFrame)

EVT_SELECED_CHANGED (wxID_ANY, MyFrame::OnSelectFront)

END_EVENT_TABLE()

void MyFrame::OnSelectFront(wxCommandEvent& event)

{}

这就是利用已有事件类自定义事件的全部了,如果我们希望事件类提供给我们更多的参数,那么我们就需要自己定义一个类来替换wxCommandEvent,其他的步骤相同,下一节介绍一下如何自定义自己的事件类。

2.4.2 利用自定义的事件类自定义事件

 首先我们自定义的事件类必须直接或间接继承自wxEvent,在这里有一点需要说明wxEvent的子类中有两个比较重要的类,wxCommandEventwxNotifyEvent一般我们从这两个类继承就好了,他们都提供了一些较为特殊的功能,wxCommandEvent允许实际的上传处理而wxNotifyEvent允许我们使用Veto()来在不满足某些条件的情况下终止事件的执行。

下面我们看段事件类定义的代码

class wxFontSelectorCtrlEvent : public wxNotifyEvent

{

public:

    wxFontSelectorCtrlEvent(wxEventType commandType = wxEVT_NULL, int id = 0):

                 wxNotifyEvent(commandType, id)

    {}

 

    wxFontSelectorCtrlEvent(const wxFontSelectorCtrlEvent& event): wxNotifyEvent(event)

    {}

 

     /*

     这里我们可以定义一些属性,提供更加复杂的数据结构

     */

    void SetFontData(const wxFontData& fontData) { m_fontData = fontData; };

    const wxFontData& GetFontData() const { return m_fontData; };

    virtual wxEvent *Clone() const { return new wxFontSelectorCtrlEvent(*this); }

 

    DECLARE_DYNAMIC_CLASS(wxFontSelectorCtrlEvent);

private:

  wxFontData  m_fontData;

 

};

这里有几点需要我们注意

1)  首先我们的类间接的继承自wxEvent

2)  我们的事件类必须可以是动态实现的,使用的宏DECLARE_DYNAMIC_CLASS,并且在实现文件中我们也要使用宏IMPLEMENT_DYNAMIC_CLASS(wxFontSelectorCtrlEvent, wxNotifyEvent),动态实现允许我们在允许时刻能够动态的建立一个类,这是序列化的一个基础,wxwidgets的实现方法感觉同MFC类似,内部建立了一个队列,每个队列都有一个指针指向基类,一个指针指向下一个动态实现类,关于动态实现内容,以后还会提到

3)  我们必须实现Clone函数,这个函数非常重要,前文提到了QueueEvent就是使用该函数进行复制的

4)  更为关键的是,我们要在这个类里提供一系列的数据和getXXX()setXXX()函数,为调用段提供数据,如果没有这些数据就没有必要建立自定义的事件类了。

自定义事件类的其他处理方法同上一节的一模一样,只需要将wxCommandEvent替换成我们自己的事件类就可以了。

2.4.3 自定义事件步骤总结

 1)如果需要直接或间接从wxEvent生成一个新的事件类,这个类需要提供特殊的数据、提供Clone方法,并能够动态创建

2)使用wxDEFINE_EVENT(实现文件中)或wxDECLARE_EVENT(头文件中)建立一个自定义事件

3)在我们需要引发事件的位置生成自定义事件的实例并使用ProcessEventQueueEvent 激发事件

4)在调用端使用Bind加入事件处理函数

5)如果调用端使用静态调用方式,那么我们在事件声明的头文件中定义一个事件处理函数的签名并定义一个宏帮助客户端将事件处理函数加入EVENT Table

2.5 事件驱动总结

在这一章中,我们了解了事件处理的方法、wxWidgets事件处理的模型、如何动态的建立事件以及如何自定义事件,在这里做一些总结:

1) Wxwidgets使用event table代替虚函数实现事件,这样中即具有了父类行为的继承型,又拥有更大的灵活性,我们可以使用Skip()函数替换调用父类的虚函数的方法,直接将事件路由给父类处理提供了事件处理的继承性,同时我们通过定义事件类的方式可以提供一种行为供其他的类共享,这大大的减少了继承的数量,而且也非常的灵活。

2) Wxwidgets提供了BindConnectPushEvent等函数,为事件的动态控制提供了可能;

3) 尽量使用Bind动态绑定事件,这样可以获得较高的效率和良好的移植性;

4) 用户产生事件同编程产生事件,wxwidgets只管理用户产生的事件,也就是说我们使用鼠标调整窗口大小会产生一个Resize事件但是我们使用wxWindow::SetSize是不会产生该事件的,但也有一些例外:

函数

产生事件

wxNotebook::AddPageAdvanceSelectionDeletePage

EVT_NOTBOOK_PAGE_CHANGING

wxTreeCtrl::DeleteDeleteAllItems

EVT_TREE_DELETE_ITEM

wxTreeCtrl::EditLabel

EVT_TREE_BEGIN_LABEL_EDIT

所有的wxTextCtrl方法

参考帮助手册

 

注:wxTextCtrl::SetValue 会产生wxEVT_COMMAND_TEXT_UPDATED事件,可以使用wxTextCtrl::ChangeValue替代