wxWidgets 事件驱动

来源:互联网 发布:浏览器端口号是多少 编辑:程序博客网 时间:2024/06/15 23:38
 

2.事件驱动

 众所周知,包括Windows在内的所有GUI应用程序都是事件驱动的,那么在wxwidgets中如何基于事件驱动编程?包括以下内容:

n  简单的管理事件  

n  插入事件控制

n  动态事件控制

n  自定义事件

 

2.1 wxwidgets事件概述

   事件驱动可以定义为:应用程序设置一个循环等待用户或其他的事件源产生事件,并将事件派发到相应的事件处理函数中进行处理。

   Wxwidgets的事件定义方式同MFC基本相同,但是处理方式没有采用MFC的虚函数方式,而是采取了一种更加灵活的方式,wxWidgets中所有的类都继承自wxEvtHandler. wxEvtHandler包含一个EventTable告诉wxWidgets如何路由事件。所有的事件处理函数的形式都是一致的,返回void,这些事件处理函数全部是非虚的(not virtual),事件处理函数的传入参数根据事件的不同,是不同的派生自wxEvent的类,从而保证了处理函数能够从传入参数中获得更多的有用信息。

wxwidgets 中有两种事件一种是可以回溯给父(window)事件处理函数处理的,一种是不发送的。发送的事件处理类基本都是wxCommandEvent或其子类。不发送的有:wxActivateEvent, wxCloseEvent, wxEraseEvent, wxFocusEvent, wxKeyEvent, wxIdleEvent,wxInitDialogEvent, wxJoystickEvent, wxMenuEvent, wxMouseEvent, wxMoveEvent,

wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent, wxScrollWinEventwxSysColourChangedEvent

2.2 简单的管理事件:

也称为静态事件管理,下面这个程序要在一个frame中建立一个Textbox和一个Button,单击Button能够显示TextBox中的内容,通过这个程序来介绍静态事件管理的4个步骤,

l  建立一个类使之直接或简介的继承自wxEvtHandler

//定义一个资源ID,用来标识创建项目的唯一性

#define OKBTNID   wxID_HIGHEST+21

//定义事件处理类,需要包含相应的事件处理函数

class MyFrame : public wxFrame

l  在MyFrame中加入ButtonClick处理函数

 

class MyFrame : public wxFrame

{

private:

wxButton* button;

public:

// 构造函数

MyFrame(const wxString& title);

// 事件处理函数,注意形参是wxEvent的派生类

void OnButtonOK(wxCommandEvent& event);

private:

// 声明event table

DECLARE_EVENT_TABLE()

};

l  在实现文件中建立映射

BEGIN_EVENT_TABLE(MyFrame, wxFrame)

EVT_BUTTON(wxID_OK,MyFrame::OnButtonOK)

END_EVENT_TABLE()

 

l  编写实际处理函数

void MyFrame::OnButtonOK(wxCommandEvent &event)

{

     wxString msg;

     msg.Printf("The value is %s",txtbox->GetValue());

     wxMessageBox(msg,_T("OKButton"),wxOK,this);

}

编译、运行,通过编译后就可以显示出如下的界面:在textbox中输入一些内容,然后点击button就可以弹出一个对话框显示textbox中的内容了。上面的小例子显示一个标准事件的创建和控制的过程,一共4个步骤,简单回顾一下记住一些要点:

1)  创建事件处理的类,并在其中加入事件处理函数

2)  在事件处理类中加入事件处理表使用宏 DECLARE_EVENT_TABLE()

3)  在实现文件中填写事件处理表,并将事件同处理函数进行对接利用到这么几个宏:BEGIN_EVENT_TABLEEND_EVENT_TABLEEVT_XXXXXX

4)  编写处理函数。

下面是一些控制事件的要点:

l  负责事件处理的类必须直接或间接继承自wxEvtHandler

l  事件处理函数返回值一定是void,根据事件的类型不同,事件处理函数的传入参数的类型型也不同,但都继承自wxEvent,我们可以通过这个参数获得当前消息的各种信息

l  实际上每个EVT_XXXX宏都创建了一个wxEventTableEntry结构体并把他们存入由DECLARE_EVENT_TABLE定义的一个数组sm_eventTableEntries中,

BEGIN_EVENT_TABLE中声明了一个由eventTableEntries建立的静态HASH表。并强制其init标志为TRUE,在今后的查询中wxwidgets会调用该HASH表的初始化过程将sm_eventTableEntries的内容填充到hash表中提高查询效率,这是一种延迟的机制,并不是在初始化的时候就建立该HASH表,而是在首次查询的时候进行。这部分的处理机制在今后的源代码分析中在详细的进行讨论。


2.3 更加灵活的事件控制方式

MFC中,我们控制一个已有控件的行为可以通过子类继承的的方式。在wxWidgets中提高了两种更加方便的方式对已有的事件处理程序进行扩展那就是动态方式和 插入方式。

2.3.1 动态方式

所有从wxevtHandler继承的类都提供了两个特殊的函数BindConnectConnect是一种以前版本的处理方式,Bindconnect的替代方法,他使用了模板方法更加灵活和安全,如果是新程序建议不要使用Connect而直接应该使用Bind

我们在讨论事件处理的时候总是提到的是静态方式,然而wxwidgets为我们提供了一种在运行时刻维护event table的方法,暂时称为动态方式吧。我们可以在某一个特定的时刻让我们的控件(窗口)对同一个事件产生不同的行为,这是非常cool的一种方法,而且通过动态方法,我们可以使得同一种控件(窗体)在某种情况下产生不同的行为。

这是Bind的函数定义,我只列出一种定义其他的定义我们可以通过帮助自行进行了解。

template<typename EventTag , typename Class , typename EventArg , typename EventHandler >

void wxEvtHandler::Bind

(

const EventTag & 

eventType,

  

void(Class::*)(EventArg &) 

method,

  

EventHandler * 

handler,

  

int 

id = wxID_ANY,

  

int 

lastId = wxID_ANY,

  

wxObject

userData = NULL

 

 

)

  

[inline]

Parameters:

 

eventType 

事件类型如:wxEVT_CHAR是wxEventTypeTag模板类的一个实例.

 

method 

事件处理函数.

 

handler 

 事件处理函数的所属类的实例指针

 

id 

 变换事件处理控件的资源id(起始值)

 

lastId 

变换事件处理控件的资源id(结束值)由idlastid确定了一个范围 

 

userData 

同事件相关的数据类,.

 

我们来看一个例子,我们自己做一个Textbox使得他只能接受字符,其实使用Bind可以更加灵活,我只是想通过这个例子对应MFC中的子类化

#define ID_MyText wxID_HIGHEST+1

class MyTextCtrl:public wxTextCtrl

{

public :

    MyTextCtrl(wxWindow *parent,wxWindowID id)

        : wxTextCtrl(parent,id,_T("MyText"),wxPoint(30,50),wxSize(100,26))

     {

         Bind(wxEVT_CHAR,&MyTextCtrl::OnChar,this, ID_MyText);  

     }

 void OnChar(wxKeyEvent&event);

};

注意这里的OnChar是一个事件处理函数,前面说个了wxwidgets不使用虚函数机制进行事件处理,因此我们的OnChar并没有override wxtextCtrlonchar函数,在myTextCtrl中我们使用Bind替换了wxTextCtrl中的按键事件的处理函数。

然后我们在myframe中建立我们自己的控件就可以了。

MyTextCtrl* txtbox=new MyTextCtrl(this, ID_MyText);

 

void MyTextCtrl::OnChar(wxKeyEvent &event)

{

     if(wxIsalpha(event.m_keyCode))

     {

         event.Skip();

     }

     else

     {

         wxBell();

     }

}

这里由一点需要注意的就是event.Skip,它有点想windowsdefaultwndProc当我们不想处理的就调用skip(),由默认行为接管。

2.3.2 插入方式:

   插入方式同动态方式类似,也是改变控件行为的一种方式,同样你不必从window类继承一个新类来处理事件,你可以直接从wxEvtHandler建立一个新的事件处理类,然后使用PushEventHandler将新的事件处理行为加入到已有控件或窗体的事件处理表中,这个被加入的新事件处理方法会先接受事件,如果你的事件处理函数没有对事件进行处理,wxwidgets才会去搜索事件处理表,你也可以用PopEventHandler将事件加入的事件移除,并且如果你提供了true作为实参的化这个类就会被删除。使用这种机制我们可以定义一种行为,供很多的控件进行使用,非常的方便。同样是上个例子,我们来换一种实现方式。

 首先定义一个新类,并处wxEvtHanler继承

class TextctrlEVHandler:public wxEvtHandler

{

 DECLARE_EVENT_TABLE()

public:

 void OnChar(wxKeyEvent&event);

};

需要注意我们自己定义了eventtable,并且在实现文件中我们需要用BEGIN_EVENT_TABLEEND_EVENT_TABLEEVT_XXXXXX

BEGIN_EVENT_TABLE(TextctrlEVHandler, wxEvtHandler)

EVT_CHAR(TextctrlEVHandler::OnChar)

END_EVENT_TABLE()

 

void TextctrlEVHandler::OnChar(wxKeyEvent &event)

{

     if(wxIsalpha(event.m_keyCode))

     {

         event.Skip();

     }

     else

     {

         wxBell();

     }

}

在需要的时候我们将这个事件类Push到相应的位置

 

 wxTextCtrl* tctrl= new wxTextCtrl(this,wxID_Any,_T("Hello"),wxPoint(0,0),wxSize(100,26));

 TextctrlEVHandler* myevhandler=new TextctrlEVHandler();

 tctrl->PushEventHandler(myevhandler);

此时的tctrl就具有了只接受字符,不接受数字的能力了。但我们一定要注意push进去的类在退出程序前一定要pop出来,否则会引发一个异常,语法如下:

tctrl->PopEventHandler(true);

这里需要说明的是另外一点内容wxID_Any这个资源识别符,我们对这类识别符并不陌生,就相当于我们给控件的名字,wxID_Any在系统中的定义是-1,也就是说当我们不关心控件的ID时可以使用wxID_Any作为识别符,这样编译器会自动给我们分配一个。WxWidget对控件ID的定义是在同一个父下识别符必须唯一,换句话说也就是不同父的控件可以使用相同的资源识别符,还有我们可以自定义这个符号,系统占用了从wxID_LOWESTwxID_HIGHEST的识别符号,我们自定义的识别符可以从wxID_HIGHEST+1开始。

2.4用户自定义事件
如果出于某种问题的考虑,你可能需要你自己的类具备一些特殊的行为或事件,比如我制作了一个类似Word的文字编辑程序,其中有一个选择字体的自定义控件,我希望在选择完字体后,系统替我修改我选中文字的字体。这是就需要产生一个SELECTED_CHANGED事件在这一事件中进行一些后处理工作。那么这就用到了自定义事件。自定义事件有两种,首先是基于已有的事件类。其次是自己建立一个事件类,我们先看看第一种比较简单的情况如何实现。
2.4.1 利用已有事件类建立自定义事件
      wxWidgets为我们提供了大量的基础事件类,如:wxCommandEvent、wxNotifyEvent、wxThreadEvent、wxScrollEvent等等。我们可以直接拿来使用,在使用前我们需要了解两个宏,wxDECLARE_EVENT和wxDEFINE_EVENT,这两个宏都是为我们定义自己的事件类型服务的。正常情况下,我们只需要使用wxDEFINE_EVENT,如果我们需要在.h文件中使用我们定义的事件,那么在.h文件中需要引入wxDECLARE_EVENT.
如果我们在实现文件中定义 wxDEFINE_EVENT(wxEVT_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的子类中有两个比较重要的类,wxCommandEvent和 wxNotifyEvent。一般我们从这两个类继承就好了,他们都提供了一些较为特殊的功能,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)在我们需要引发事件的位置生成自定义事件的实例并使用ProcessEvent或QueueEvent 激发事件
4)在调用端使用Bind加入事件处理函数
5)如果调用端使用静态调用方式,那么我们在事件声明的头文件中定义一个事件处理函数的签名并定义一个宏帮助客户端将事件处理函数加入EVENT Table
2.5 事件驱动总结
在这一章中,我们了解了事件处理的方法、wxWidgets事件处理的模型、如何动态的建立事件以及如何自定义事件,在这里做一些总结:
1) Wxwidgets使用event table代替虚函数实现事件,这样中即具有了父类行为的继承型,又拥有更大的灵活性,我们可以使用Skip()函数替换调用父类的虚函数的方法,直接将事件路由给父类处理提供了事件处理的继承性,同时我们通过定义事件类的方式可以提供一种行为供其他的类共享,这大大的减少了继承的数量,而且也非常的灵活。
2) Wxwidgets提供了Bind、Connect、PushEvent等函数,为事件的动态控制提供了可能;
3) 尽量使用Bind动态绑定事件,这样可以获得较高的效率和良好的移植性;
4) 用户产生事件同编程产生事件,wxwidgets只管理用户产生的事件,也就是说我们使用鼠标调整窗口大小会产生一个Resize事件但是我们使用wxWindow::SetSize是不会产生该事件的,但也有一些例外:
函数
 产生事件

函数

产生事件

wxNotebook::AddPage、AdvanceSelection、DeletePage

EVT_NOTBOOK_PAGE_CHANGING

wxTreeCtrl::Delete、DeleteAllItems

EVT_TREE_DELETE_ITEM

wxTreeCtrl::EditLabel

EVT_TREE_BEGIN_LABEL_EDIT

所有的wxTextCtrl方法

参考帮助手册



原创粉丝点击