发布者: Tank
上一部分分析了wxWidgets事件机制实现时一些基本概念,和所涉及到的数据结构。基于此继续讨论事件哈希表的建立,事件表相关宏处理的背后, 触发事件处理的方式,以及事件在不同平台的分发过程。
在wxEventHashTable
构造函数里面,并没有构建哈希表,而是用一个布尔变量标识哈希表尚未重建,采用这种延迟重建的方式来实现哈希表, 哈希表真正建立,发生在第一次查找事件表时,重建以后,设置布尔变量,以后只要对哈希表查找即可,从而加速了事件表的查找。 哈希函数采用除以size取余的方法。
以下是wxEventTable
-> wxEventHashTable
的过程
table = table->base
,转1继续对父类事件表哈希
void wxEventHashTable::InitHashTable()
{
// Loop over the event tables and all its base tables.
const wxEventTable *table = &m_table;
while (table)
{
// Retrieve all valid event handler entries
const wxEventTableEntry *entry = table->entries;
while (entry->m_fn != 0)
{
// Add the event entry in the Hash.
AddEntry(*entry);
entry++;
}
table = table->baseTable;
}
//....
}
void wxEventHashTable::AddEntry(const wxEventTableEntry &entry)
{
//..
EventTypeTablePointer *peTTnode = &m_eventTypeTable[entry.m_eventType % m_size];
EventTypeTablePointer eTTnode = *peTTnode;
if (eTTnode)
{
if (eTTnode->eventType != entry.m_eventType)
{
// Resize the table!
GrowEventTypeTable();
// Try again to add it.
AddEntry(entry);
return;
}
}
else
{
eTTnode = new EventTypeTable;
eTTnode->eventType = entry.m_eventType;
*peTTnode = eTTnode;
}
// Fill all hash entries between entry.m_id and entry.m_lastId...
eTTnode->eventEntryTable.Add(&entry);
}
对于上面的第3步,把事件条目加入哈希表,wxEventTableEntry -> wxEventHashTable
的过程
m_eventTypeTable
的下标 m_eventTypeTable[i] = 0
,说明该事件类型事件类型表尚未填入哈希表,转4 ,否则转5 eventEntryTable
,结束 wxEvtHandler
中有三个静态成员,sm_eventTable
sm_eventHashTable
分别代表当前事件处理类事件表和事件哈希表, 要被子类连接事件表所有为protected。所有要处理事件的类继承wxEvtHandler
,需要重新定义这个两个静态成员。 因此两个虚函数分别返回当前类中事件表和事件哈希表。另一个私有的静态成员sm_eventTableEntries
表示当前类的 事件条目数组,用于构成事件表。
class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
{
//..
private:
static const wxEventTableEntry sm_eventTableEntries[];
protected:
static const wxEventTable sm_eventTable;
virtual const wxEventTable *GetEventTable() const;
static wxEventHashTable sm_eventTable;
virtual wxEventHashTable& GetEventHashTable() const;
//..
}
在类的声明中使用DECLARE_EVENT_TABLE()
,实际上市覆盖wxEvtHandler
中的事件表和事件哈希表,并重写返回当前 类事件表和事件哈希表的虚函数
#define DECLARE_EVENT_TABLE() \
private: \
static const wxEventTableEntry sm_eventTableEntries[]; \
protected: \
static const wxEventTable sm_eventTable; \
virtual const wxEventTable* GetEventTable() const; \
static wxEventHashTable sm_eventHashTable; \
virtual wxEventHashTable& GetEventHashTable() const;
在类的实现中使用BEGIN_EVENT_TABLE
和END_EVENT_TABLE()
实际上初始化当前类的事件表(一个静态成员), 事件表父指针指向父类事件表,事件表的条目指针事件等于事件条目数组首地址。事件哈希表有事件表生成, 这里还没有构造,值是分配了31个为0的空间。中间的一堆事件映射宏实际上是用一堆5元组初始化当前类的事件条目表。
#define BEGIN_EVENT_TABLE(theClass, baseClass) \
const wxEventTable theClass::sm_eventTable = \
{ &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; \
const wxEventTable *theClass::GetEventTable() const \
{ return &theClass::sm_eventTable; } \
wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable); \
wxEventHashTable &theClass::GetEventHashTable() const \
{ return theClass::sm_eventHashTable; } \
const wxEventTableEntry theClass::sm_eventTableEntries[] = { \
EVT_MENU(Minimal_About, MyFrame::OnAbout)
DECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL),
#define END_EVENT_TABLE() DECLARE_EVENT_TABLE_ENTRY( wxEVT_NULL, 0, 0, 0, 0 ) };
这样每个事件处理的类会用事件映射宏的5元组,构造事件条目表,事件条目表首地址, 放在事件表中,同时事件表会记录父类的事件表地址。哈希表初始化为一堆0地址,搜索一次后, 会把当前事件表和父类所有的事件表哈希到当前事件哈希表中,这样以后该类的对象, 事件表查找,只要查找已经构建好的事件哈希表, 事件复杂度近似O(1)。
上面已经分析过事件处理的流程,那么什么情况会触发一个事件被处理呢?有以下几种方式 * 窗口或控件感应到操作(来自用户的操作引发事件处理流程) * 调用wxTheApp->ProcessPendingEvents()
* 调用wxEvtHandler::ProcessEvent(wxEvent &event)
(应用程序自己调用)
wx保存了一个全局的指针链表,里面保存当前应用程序所有事件处理类wxEvtHandler
对象的指针
/* code: common/event.cpp:144 */
wxList *wxPendingEvents = (wxList *)NULL;
对于所有事件类的基类wxEvtHandler
,有个成员m_pendingEvents
保存当前wxEvtHandler
对象的未决事件
class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
{
//..
wxList* m_pendingEvents;
//..
}
遍历全局链表未决事件链表wxPendingEvents
,取出所有的wxEvtHandler
,然后调用每个wxEvtHandler
上的ProcessPendingEvents()
方法
/* code: src/common/Appbase.cpp:267 */
void wxAppConsole::ProcessPendingEvents()
{
// ...
// iterate until the list becomes empty
wxList::compatibility_iterator node = wxPendingEvents->GetFirst();
while (node)
{
wxEvtHandler *handler = (wxEvtHandler *)node->GetData();
wxPendingEvents->Erase(node);
// In ProcessPendingEvents(), new handlers might be add
// and we can safely leave the critical section here.
wxLEAVE_CRIT_SECT( *wxPendingEventsLocker );
handler->ProcessPendingEvents();
wxENTER_CRIT_SECT( *wxPendingEventsLocker );
node = wxPendingEvents->GetFirst();
}
// ...
}
wxEvtHandler
上的ProcessPendingEvents()
方法中wxEvtHandler
遍历处理自己未决链表m_pendingEvents
的事件
void wxEvtHandler::ProcessPendingEvents()
{
//..
size_t n = m_pendingEvents->size();
for ( wxList::compatibility_iterator node = m_pendingEvents->GetFirst();
node;
node = m_pendingEvents->GetFirst() )
{
wxEventPtr event(wx_static_cast(wxEvent *, node->GetData()));
// It's important we remove event from list before processing it.
// Else a nested event loop, for example from a modal dialog, might
// process the same event again.
m_pendingEvents->Erase(node);
wxLEAVE_CRIT_SECT( Lock() );
ProcessEvent(*event);
wxENTER_CRIT_SECT( Lock() );
if ( --n == 0 )
break;
}
//..
}
wxEvtHandler
提供了向自己接未决链表加入事件的方法,wxEvtHandler:AddPendingEvent
, 注意这里只是把event clone一份,然后加入未决链表, 并不处理就返回。
void wxEvtHandler:AddPendingEvent(wxEvent& event)
wxEvtHandler::ProcessEvent用于立即处理这个事件。
bool wxEvtHandler::ProcessEvent(wxEvent& event)
上述所有wxEvent
相关的东西都是跨平台的,wxEvent是wx定义出来的一个wx事件类, wx程序中处理的都是wxEvent,并不设计到具体平台上的消息处理。然后实际上, 对于GUI程序设计每个平台都有消息循环、消息分发机制,wx为了跨平台提供了一个抽象层, 屏蔽了平台相关的消息处理部分。在include/wx下的头文件提供了wx对外公共的接口, 利用里面的接口我们可以编写平台无关的GUI的程序,然而include/wx中头文件接口的实现却依赖于具体的平台, 实际上上平台相关的接口位于/include/wx/gtk /include/wx/msw等,但用户不必关心这里平台相关的细节, 只要利用提供的公共接口编程即可。下面分析wx中消息分发以及在不同平台的实现。
wx程序启动流程如下: * 建立个wxApp子类MyApp的实例 * 调用MyApp中重写的虚函数wxApp::OnInit
完成初始化(主要是创建顶层窗口),返回false结束 * 调用AppBase::OnRun->wxAppBase::MainLoop
进入消息循环
/* src/common/appcmn.cpp::357 */
int wxAppBase::OnRun()
{
// see the comment in ctor: if the initial value hasn't been changed, use
// the default Yes from now on
if ( m_exitOnFrameDelete == Later )
{
m_exitOnFrameDelete = Yes;
}
//else: it has been changed, assume the user knows what he is doing
return MainLoop();
}
// .....
int wxAppBase::MainLoop()
{
wxEventLoopTiedPtr mainLoop(&m_mainLoop, new wxEventLoop);
return m_mainLoop->Run();
}
/* inclucde/wx/app.h:329 */
class WXDLLIMPEXP_CORE wxAppBase : public wxAppConsole
{
//..
wxEventLoop *m_mainLoop;
//..
}
m_mainLoop
是个事件循环类wxEventLoop
的对象指针,wxEventLoop
是个平台相关的事件循环类,从此进入不同平台的消息循环
WinMain -> wxEntry -> wxEntryReal -> wxAppBase::OnRun -> wxAppBase::MainLoop ->
wxEventLoopManual::Run -> wxEventLoop::Dispatch -> wxEventLoop::ProcessMessage
/* src/common.Evtloopcmn.cpp:65 */
int wxEventLoopManual::Run()
{
while ( !Pending() && (wxTheApp && wxTheApp->ProcessIdle()) )
;
if ( m_shouldExit )
{
while ( Pending() )
Dispatch();
break;
}
}
程序进入一个无限的循环,如果没消息处理,就处理idle消息,有消息就分发消息
bool wxEventLoop::Dispatch()
{
// ..
MSG msg;
BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);
ProcessMessage(&msg);
//..
}
取消息处理消息,这里的消息是WXMSG *msg
typedef struct tagMSG WXMSG;
在Windows程序中,消息是由MSG
结构体来表示的。MSG结构体的定义如下(参见MSDN):
typedef struct tagMSG { // msg
HWND hwnd; //标识窗口过程接收消息的窗口
UINT message; //指定消息号
WPARAM wParam; //指定有关消息的附加信息。 确切含义取决于 message 成员的值
LPARAM lParam; //指定有关消息的附加信息。 确切含义取决于 message 成员的值。
DWORD time; //指定消息已传递的时间
POINT pt; //当消息已传递了,指定光标位置,在屏幕坐标
} MSG;
这里的消息就是windows中的消息了
void wxEventLoop::ProcessMessage(WXMSG *msg)
{
// give us the chance to preprocess the message first
if ( !PreProcessMessage(msg) )
{
// if it wasn't done, dispatch it to the corresponding window
::TranslateMessage(msg);
::DispatchMessage(msg);
}
}
这里就是win32 SDK中的消息循环了,说白了wx中的事件处理,最终还是用了平台上的消息机制, wx只是完成了跨平台的封装,对用户屏蔽了平台相关的是实现细节,抽象出一个用户直接利用的抽象层。
windows中的消息WXMSG
怎么转换成wxEvent*
wxEntryReal -> wxEntryStart -> wxApp::Initialize() -> wxApp:: RegisterWindowClasses()
注册一个窗口类,这个窗口类绑定了窗口处理过程
/* src/msw/app.cpp */
bool wxApp::RegisterWindowClasses()
{
WNDCLASS wndclass;
wxZeroMemory(wndclass);
// for each class we register one with CS_(V|H)REDRAW style and one
// without for windows created with wxNO_FULL_REDRAW_ON_REPAINT flag
static const long styleNormal = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
static const long styleNoRedraw = CS_DBLCLKS;
// the fields which are common to all classes
wndclass.lpfnWndProc = (WNDPROC)wxWndProc;
wndclass.hInstance = wxhInstance;
wndclass.hCursor = ::LoadCursor((HINSTANCE)NULL, IDC_ARROW);
// register the class for all normal windows
wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wndclass.lpszClassName = wxCanvasClassName;
wndclass.style = styleNormal;
if ( !RegisterClass(&wndclass) )
{
wxLogLastError(wxT("RegisterClass(frame)"));
}
//..
}
然后里创建窗口
/* src/msw/window.cpp */
bool wxWindowMSW::MSWCreate(const wxChar *wclass,
const wxChar *title,
const wxPoint& pos,
const wxSize& size,
WXDWORD style,
WXDWORD extendedStyle)
{
wxString className(wclass);
if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
{
className += wxT("NR");
}
// do create the window
wxWindowCreationHook hook(this);
m_hWnd = (WXHWND)::CreateWindowEx
(
extendedStyle,
className,
title ? title : m_windowName.c_str(),
style,
x, y, w, h,
(HWND)MSWGetParent(),
(HMENU)controlId,
wxGetInstance(),
NULL // no extra data
);
if ( !m_hWnd )
{
wxLogSysError(_("Can't create window of class %s"), className.c_str());
return false;
}
SubclassWin(m_hWnd);
}
其中,void wxWindowMSW::SubclassWin(WXHWND hWnd)
用于设置新窗口的窗口处理过程
WXLRESULT wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
//...
switch ( message )
{
case WM_CREATE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MBUTTONDBLCLK:
}
可见MSW 版本中的事件wxEvent由windows中消息WN_XX
而来
程序启动后,GTK版本进入的gtk的wxEventLoop事件循环类,GTK是一种事件驱动工具包,这意味着它将在gtk_main
函数 中一直等待,直到事件发生和控制权被传递给相应的函数。gtk_main()
是在每个GTK应用程序都要调用的函数。 当程序运行到这里时, Gtk将进入等待态,等候X事件(比如点击按钮或按下键盘的某个按键)、Timeout 或文件输入/输出发生。
/* src/gtk/evtloop.cpp */
int wxEventLoop::Run()
{
// event loops are not recursive, you need to create another loop!
wxCHECK_MSG( !IsRunning(), -1, _T("can't reenter a message loop") );
wxEventLoopActivator activate(this);
m_impl = new wxEventLoopImpl;
gtk_main();
OnExit();
int exitcode = m_impl->GetExitCode();
delete m_impl;
m_impl = NULL;
return exitcode;
}
-