自定义事件
大体方法
就像每个事件被其事件类型所唯一确定一样,定义一个自定义事件从为它定义一个新事件类型开始。这通过使用wxDEFINE_EVENT()宏来完成。就像事件类型是可变的,如果有必要它也可以通过使用wxDECLARE_EVENT()宏来声明。
另一件要做的事情就是决定你是否需要为事件定义一个自定义事件类或使用已经存在的类,代表性的有wxEvent(它不会提供任何额外的信息)或wxCommandEvent(它包含几种额外的域,并且默认把事件向上传递)。这两种方式的细节都将都将在下面描述。代码详细说明和使用自定义事件类型的完整例子也可以在事件示例中查看。
最后你将需要产生并发送你的自定义事件。产生事件就像实例化你的自定义事件类并初始化其内部值那样简单。为了发送事件到一个特定的事件句柄,这里有两种可选:使用wxEvtHandler::AddPendingEvent或wxEvtHandler::QueueEvent。当进行内部线程通信时,你基本上只需要使用后者。当你只使用主线程时,你也可以安全的使用前者。
最后需要注意的是,还有两个简单的全局封装函数关联到提到的那两个wxEvtHandler函数,即wxPostEvent() 和 wxQueueEvent()。
使用存在的事件类
如果你仅打算使用wxCommandEvent和一个新事件类型,可以使用下面列出的事件表宏之一而不用自己定义一个新事件类。
Example:
wxDECLARE_EVENT(MY_EVENT, wxCommandEvent);//这句很明显应该位于头文件中:它仅仅声明了MY_EVENT这个事件类型
wxDEFINE_EVENT(MY_EVENT, wxCommandEvent);//这是事件类型定义,所以不能位于头文件
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)//用事件表处理事件的示例代码
EVT_MENU (wxID_EXIT, MyFrame::OnExit)
...
EVT_COMMAND (ID_MY_WINDOW, MY_EVENT, MyFrame::OnMyEvent)
wxEND_EVENT_TABLE()
void MyFrame::OnMyEvent(wxCommandEvent& event)//事件处理函数
{
// do something
wxString text = event.GetString();
}
MyFrame::MyFrame()// 使用Bind<>()处理事件的示例代码:
{
Bind(MY_EVENT, &MyFrame::OnMyEvent, this, ID_MY_WINDOW);
}
void MyWindow::SendEvent()// 产生事件的示例代码
{
wxCommandEvent event(MY_EVENT, GetId());
event.SetEventObject(this);
event.SetString("Hello");// 给它一些包含的信息
ProcessWindowEvent(event);//发送它
}
实验图:
定义你自己的事件类
在一些环境下,你必须定义你自己的事件类,例如从一个地方发送一些很复杂的数据到另一个地方。除了定义你的事件类,如果你打算事件表来处理这种类型的事件,你还需要定义你自己的事件表宏。
Here is anexample:
class MyPlotEvent: public wxEvent// 定义新事件类
{
public:
MyPlotEvent(wxEventType eventType, int winid, const wxPoint& pos)
: wxEvent(winid, eventType),
m_pos(pos)
{
}
wxPoint GetPoint() const { return m_pos; }// 获取成员变量
virtual wxEvent *Clone() const { return new MyPlotEvent(*this); }//实现纯虚函数
private:
const wxPoint m_pos;
};
//我们定义了一个关联到上面的类的单独的事件类型 MY_PLOT_CLICKED ,但很明显你需要不止一个事件类型,例如,
//你还可以拥有MY_PLOT_ZOOMED或MY_PLOT_PANNED,在这种情况下你只需要在这添加更多相似的内容。
wxDEFINE_EVENT(MY_PLOT_CLICKED, MyPlotEvent);
/**************************************************************************************************/
// 如果你想支持旧编译器你需要使用下面这些繁琐的宏:
typedef void (wxEvtHandler::*MyPlotEventFunction)(MyPlotEvent&);
#define MyPlotEventHandler(func) wxEVENT_HANDLER_CAST(MyPlotEventFunction, func)
//如果你的代码使用现在流行的编译器编译,你就可以这样做:
#define MyPlotEventHandler(func) (&func)
//最后定义一个宏为新的事件类型创建一个完整的事件表
//记住如果你使用Bind<>() 你就完全不需要这些,你可以仅使用 &func来代替MyPlotEventHandler(func),除非你用的是非常老的编译器。
#define MY_EVT_PLOT_CLICK(id, func) \
wx__DECLARE_EVT1(MY_PLOT_CLICKED, id, MyPlotEventHandler(func))
//处理事件的示例代码(你将使用两者之一,而不是两个都用)
事件表:
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_PLOT(ID_MY_WINDOW, MyFrame::OnPlot)
wxEND_EVENT_TABLE()
动态绑定:
MyFrame::MyFrame()
{
Bind(MY_PLOT_CLICKED, &MyFrame::OnPlot, this, ID_MY_WINDOW);
}
void MyFrame::OnPlot(MyPlotEvent& event)
{
... do something with event.GetPoint() ...
}
// 产生事件的示例代码:
void MyWindow::SendEvent()
{
MyPlotEvent event(MY_PLOT_CLICKED, GetId(), wxPoint(...));
event.SetEventObject(this);
ProcessWindowEvent(event);
}
实验图如下:
需要注意的是这句:
#define MY_EVT_PLOT_CLICK(id, func) \
wx__DECLARE_EVT1(MY_PLOT_CLICKED, id, MyPlotEventHandler(func))
\是继续符,代表下一行接本行,我本来以为上面那个是说明文档里出错了,实际上是我错了。。还有里面有两个_接起来,我本来以为怎么可能起这种宏名,没想到真的是两个。。还有一个就是标识id是窗口id,我原来理解不到位,把触发事件的按钮的id当多了标识id,结果事件找不到对应的处理函数了。。。。
再一个,这句:
#define MyPlotEventHandler(func) (&func)
不知道干嘛用的,我不用这句,使用事件表依旧工作正常。。。
事件表的向上传递寻找之类的我现在也没怎么搞清楚,我从一个线程传递事件到非父窗口的窗口时大概如下:
自定义事件类:
class MyThreadEvent: public wxThreadEvent
{
public:
MyThreadEvent(wxEventType eventType, int winid,unsigned char* pdate);
virtual wxEvent *Clone() const {return new MyThreadEvent(*this);}
unsigned char* GetPointer() const {return p;};
private:
unsigned char* p;
};
事件类型也定义声明Ok:
头文件中:
wxDECLARE_EVENT(WX_POINTER_ARR,MyThreadEvent);
cpp中:
wxDEFINE_EVENT(WX_POINTER_ARR,MyThreadEvent);
然后写好事件映射宏:
#define EVT_POINTER_ARR(id,fn) DECLARE_EVENT_TABLE_ENTRY(WX_POINTER_ARR\
,id\
,-1\
,&fn\
,(wxObject*)NULL\
),
注意最后的逗号不要丢,而\是继续符。
事件表:
BEGIN_EVENT_TABLE(showpic,wxFrame)
EVT_POINTER_ARR(wxID_ANY,showpic::OnProcessThreadEvent)
END_EVENT_TABLE()
事件处理函数
void showpic::OnProcessThreadEvent(MyThreadEvent& event)
{
image.Destroy();//清除原来的图片
image=wxImage(col,row,event.GetPointer());//生成新的图片
bitmap = wxBitmap(image.Scale(800,600));//调整大小
Refresh(false);
}
最后是产生事件:
wxQueueEvent(windows,new MyThreadEvent(WX_POINTER_ARR,wxID_ANY,imgdate));
其中windows是处理事件的窗口的指针。
关于事件的产生
| 虚拟 |
事件放入队列以便进行后续处理。
该方法类似于ProcessEvent(),但是后者是同步的,即事件在函数返回之前立即被处理,这一个是异步的,并且将在稍后的时间(通常在下一个事件期间)处理事件循环迭代)。
另一个重要的区别是这个方法会获取事件参数的所有权,即它会自动删除它。这意味着事件应该在堆上分配,并且在函数返回之后指针不能再被使用(因为它可以随时被删除)。
QueueEvent()可用于从工作线程到主线程的线程间通信,它在内部使用锁定,并通过确保正在调用的线程不会再使用事件对象来避免AddPendingEvent()文档中提到的问题。应该注意避免该对象的某些字段被它使用,特别是事件对象的任何wxString成员不能是另一个wxString对象的浅层副本,因为这将导致它们在后台仍然使用相同的字符串缓冲区。例如:
请注意,您可以使用wxThreadEvent而不是wxCommandEvent来避免此问题:
最后注意到,如果通过调用wxWakeUpIdle()当前处于空闲状态,则此方法会自动唤醒事件循环,因此在使用它时不需要手动执行。
-
以来
- 2.9.0
-
参数
-
事件 要排队的堆分配事件,QueueEvent()拥有它的所有权。该参数不应该 NULL
。
在wxWindow中重新实现。
| 虚拟 |
发布事件稍后处理。
这个功能类似于QueueEvent() ,但不能用于发布来自工作线程事件与事件对象wxString场(即在实践中大多数),因为不安全使用相同的wxString对象,是因为该wxString原始事件对象中的字段及其由该函数内部创建的副本在内部共享相同的字符串缓冲区。使用QueueEvent()来避免这种情况。
一个事件副本由函数进行,所以一旦函数返回(原来是在堆栈上创建的),原来的代码就可以被删除。这要求wxEvent :: Clone()方法由事件实现,以便可以将其复制并存储直到被处理。
-
参数
-
事件 事件添加到挂起的事件队列。
在wxWindow中重新实现。
| 虚拟 |
处理事件,搜索事件表并调用零个或多个合适的事件处理函数。
通常,您的应用程序不会调用此函数:它在wxWidgets实现中调用,以将传入的用户界面事件分派到框架(和应用程序)。
但是,如果实现定义新事件类型的新功能(例如新控件),则可能需要调用它,而不是允许用户覆盖虚拟函数。
请注意,您通常不需要重写ProcessEvent()来自定义事件处理,覆盖特别提供的TryBefore()和TryAfter()函数通常就足够了。例如,wxMDIParentFrame可以覆盖TryBefore(),以确保在父框架本身处理之前在活动子帧中处理菜单事件。
事件表搜索的正常顺序如下:
- wxApp :: FilterEvent()被调用。如果它返回任何东西
-1
(默认),处理在这里停止。 - TryBefore()被调用(这是wxWalidator被考虑在wxWindow对象的地方)。如果返回true,则退出该函数。
- 如果对象被禁用(通过调用wxEvtHandler :: SetEvtHandlerEnabled),该函数跳到步骤(7)。
- 使用Bind <>()绑定的处理程序的动态事件表在最近期绑定到最早绑定的顺序中进行搜索。如果找到一个处理程序,它将被执行,并且该函数返回true,除非使用wxEvent :: Skip()处理程序来表示它没有处理事件,在这种情况下搜索继续。
- 使用事件表宏绑定的处理程序的静态事件表按源代码中事件表宏的出现顺序搜索该事件处理程序。如果失败,则会尝试使用基类事件表,直到不再存在表或找到适当的函数为止。如果找到一个处理程序,则与上一步相同的逻辑适用。
- 搜索被应用在整个事件处理程序链上(通常链的长度为1)。这个链可以使用wxEvtHandler :: SetNextHandler()形成:
(指图像,如果
A->ProcessEvent
被调用,并且不处理事件,B->ProcessEvent
将被调用等等)。请注意,在wxWindow的情况下,您可以构建一堆事件处理程序(有关更多信息,请参阅wxWindow :: PushEventHandler())。如果任何链接的处理程序返回true,则该函数退出。 - TryAfter()被调用:对于wxWindow对象,这可能会将事件传播到窗口父(递归)。如果仍未处理该事件,则将wxTheApp对象上的ProcessEvent()作为最后一步进行调用。
注意步骤(2) - (6)在该函数调用的ProcessEventLocally()中执行。
-
参数
-
事件 事件处理。
-
返回
- 如果发现并执行了一个合适的事件处理函数,则该 值为true,该函数未调用 wxEvent :: Skip。
-
也可以看看
- SearchEventTable()
在wxWindow中重新实现。
还有这两个,它们是全局函数:
void wxQueueEvent | ( | wxEvtHandler * | dest, |
wxEvent * | 事件 | ||
) |
对给定对象进行处理排队事件。
这是围绕wxEvtHandler :: QueueEvent()的包装器,有关更多详细信息,请参阅其文档。
包含文件:
#include <wx / event.h>
-
参数
-
DEST 对象将事件排队,不能 NULL
。事件 堆分配和非 NULL
事件队列,该函数拥有它的所有权。
void wxPostEvent | ( | wxEvtHandler * | dest, |
const wxEvent& | 事件 | ||
) |
在GUI应用程序中,此函数使用wxEvtHandler :: AddPendingEvent()将事件发布到指定的dest对象。
否则,它使用wxEvtHandler :: ProcessEvent()立即调度事件。有关详细信息,请参阅各自的文档(和警告)。由于限制wxEvtHandler :: AddPendingEvent()这个函数是不是线程安全具有事件对象wxString字段,使用wxQueueEvent()来代替。
包含文件:
#include <wx / event.h>
按需调用