wxWidgets源碼分析(3) - 消息映射表
目錄
- 消息映射表
- 靜態消息映射表
- 靜態消息映射表處理過程
- 動態消息映射表
- 動態消息映射表處理過程
消息映射表
消息是GUI程序的核心,所有的操作行為均通過消息傳遞。
靜態消息映射表
使用靜態EventTable將事件號和處理代碼綁定起來,用法示例:
// 聲明 class debugWXFrame: public wxFrame { DECLARE_EVENT_TABLE() }; // 實現 BEGIN_EVENT_TABLE(debugWXFrame,wxFrame) EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu) END_EVENT_TABLE()
先看下定義, wxDECLARE_EVENT_TABLE
用於在當前類中聲明一些數據,大部分都是靜態數據,另外提供了GetEventTable
來訪問這個表;
#define DECLARE_EVENT_TABLE() wxDECLARE_EVENT_TABLE(); #define wxDECLARE_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
下面是實現,用於初始化這些靜態變量,所有的消息定義都是位於wxBEGIN_EVENT_TABLE
和END_EVENT_TABLE
之間:
sm_eventTableEntries
保存消息映射表,也就是消息ID和消息處理函數關系;- 實現
GetEventTable
方法供調用者使用; sm_eventHashTable
用於保存HashTable,查找速率會提升;
#define BEGIN_EVENT_TABLE(a,b) wxBEGIN_EVENT_TABLE(a,b) #define END_EVENT_TABLE() wxEND_EVENT_TABLE() #define wxBEGIN_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[] = { #define wxEND_EVENT_TABLE() wxDECLARE_EVENT_TABLE_TERMINATOR() };
位於wxBEGIN_EVENT_TABLE
和END_EVENT_TABLE
之間的是消息處理項,比如上面的EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)
就是一項,我們來展開這項:
// 用戶使用這個宏來定義事件映射
#define EVT_MENU(winid, func) wx__DECLARE_EVT1(wxEVT_MENU, winid, wxCommandEventHandler(func))
// 事件映射內部宏
#define wx__DECLARE_EVT2(evt, id1, id2, fn) wxDECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL),
#define wx__DECLARE_EVT1(evt, id, fn) wx__DECLARE_EVT2(evt, id, wxID_ANY, fn)
// 根據傳遞進來的參數創建一個wxEventTableEntry對象
#define wxDECLARE_EVENT_TABLE_ENTRY(type, winid, idLast, fn, obj) wxEventTableEntry(type, winid, idLast, wxNewEventTableFunctor(type, fn), obj)
那麽EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)
展開後就得到:
wxEventTableEntry(wxEVT_MENU, winid, wxID_ANY,
wxNewEventTableFunctor(wxEVT_MENU, wxCommandEventHandler(fn)), NULL)
wxNewEventTableFunctor
用於實例化一個wxObjectEventFunctor
對象,wxCommandEventHandler
用於強轉消息處理函數,傳遞給wxNewEventTableFunctor
使用。
#define wxCommandEventHandler(func) wxEVENT_HANDLER_CAST(wxCommandEventFunction, func)
#define wxEVENT_HANDLER_CAST( functype, func ) ( wxObjectEventFunction )( wxEventFunction )wxStaticCastEvent( functype, &func )
inline wxObjectEventFunctor *
wxNewEventTableFunctor(const wxEventType& WXUNUSED(evtType),
wxObjectEventFunction method)
{
return new wxObjectEventFunctor(method, NULL);
}
這樣就可以靜態創建多個wxEventTableEntry
對象,加入到sm_eventTableEntries
中。
靜態消息映射表處理過程
消息處理過程,wxEvtHandler::TryHereOnly
會調用靜態表查詢處理:
bool wxEvtHandler::TryHereOnly(wxEvent& event)
{
// Then static per-class event tables
if ( GetEventHashTable().HandleEvent(event, this) )
return true;
}
上面GetEventHashTable
方法將返回一個wxEventHashTable
對象,隨後調用這個對象的HandleEvent
方法:
wxEventHashTable
根據消息類型將Table分段,這樣可以快速查找到指定類型的table;- 遍歷指定類型的table,對每個
wxEventTableEntry
調用ProcessEventIfMatchesId
bool wxEventHashTable::HandleEvent(wxEvent &event, wxEvtHandler *self)
{
// Find all entries for the given event type.
wxEventType eventType = event.GetEventType();
const EventTypeTablePointer eTTnode = m_eventTypeTable[eventType % m_size];
if (eTTnode && eTTnode->eventType == eventType)
{
const wxEventTableEntryPointerArray&
eventEntryTable = eTTnode->eventEntryTable;
const size_t count = eventEntryTable.GetCount();
for (size_t n = 0; n < count; n++)
{
const wxEventTableEntry& entry = *eventEntryTable[n];
if ( wxEvtHandler::ProcessEventIfMatchesId(entry, self, event) )
return true;
}
}
return false;
}
wxEvtHandler::ProcessEventIfMatchesId
處理流程如下:
- 檢查當前收到的消息是否與本項
wxEventTableEntryBase
匹配 - 調用用戶的處理函數
m_fn
執行,這裏m_fn
也是經過了好幾層的封裝,有興趣的繼續跟蹤。
bool wxEvtHandler::ProcessEventIfMatchesId(const wxEventTableEntryBase& entry,
wxEvtHandler *handler,
wxEvent& event)
{
int tableId1 = entry.m_id,
tableId2 = entry.m_lastId;
// match only if the event type is the same and the id is either -1 in
// the event table (meaning "any") or the event id matches the id
// specified in the event table either exactly or by falling into
// range between first and last
if ((tableId1 == wxID_ANY) ||
(tableId2 == wxID_ANY && tableId1 == event.GetId()) ||
(tableId2 != wxID_ANY &&
(event.GetId() >= tableId1 && event.GetId() <= tableId2)))
{
event.Skip(false);
event.m_callbackUserData = entry.m_callbackUserData;
(*entry.m_fn)(handler, event);
if (!event.GetSkipped())
return true;
}
return false;
}
動態消息映射表
動態映射表項的增加和刪除是通過wxEvtHandler::DoBind
和wxEvtHandler::DoUnbind
執行的,wxWidgets提供了多種接口執行這個操作。
需要註意的是:如果傳遞進wxEvtHandler
對象,則在處理的時候調用用戶指定的wxEvtHandler
對象進行處理;如果未指定,則直接使用當前的wxEvtHandler
對象進行處理。
提供類似功能的還有Connect
接口,但是Connect
用起來比較復雜,建議後續代碼全部使用Bind接口
,這裏不做贅述。
Bind
接口是模板函數,提供三種接口:
- 直接綁定全局函數,此時只有event類型和function參數必須,建議使用;
- 綁定Funtor函數,用戶需要自己用
wxNewEventFunctor
進行封裝,不建議使用; - 使用類內部函數,同時需要指定類指針,建議使用。
三種接口聲明如下:
template <typename EventTag, typename EventArg>
void Bind(const EventTag& eventType,
void (*function)(EventArg &),
int winid = wxID_ANY,
int lastId = wxID_ANY,
wxObject *userData = NULL)
template <typename EventTag, typename Functor>
void Bind(const EventTag& eventType,
const Functor &functor,
int winid = wxID_ANY,
int lastId = wxID_ANY,
wxObject *userData = NULL)
template <typename EventTag, typename Class,
typename EventArg, typename EventHandler>
void Bind(const EventTag &eventType,
void (Class::*method)(EventArg &),
EventHandler *handler,
int winid = wxID_ANY,
int lastId = wxID_ANY,
wxObject *userData = NULL)
動態消息映射表處理過程
與靜態消息入口相同,在wxEvtHandler::TryHereOnly
得到調用:
bool wxEvtHandler::TryHereOnly(wxEvent& event)
{
// Handle per-instance dynamic event tables first
if ( m_dynamicEvents && SearchDynamicEventTable(event) )
return true;
}
動態表查找比較簡單,動態消息表存在m_dynamicEvents
鏈表中,只要出去鏈表的每一項逐個調用ProcessEventIfMatchesId
就可以了。
bool wxEvtHandler::SearchDynamicEventTable( wxEvent& event )
{
wxList::compatibility_iterator node = m_dynamicEvents->GetFirst();
while (node)
{
wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)node->GetData();
node = node->GetNext();
if ( event.GetEventType() == entry->m_eventType )
{
wxEvtHandler *handler = entry->m_fn->GetEvtHandler();
if ( !handler )
handler = this;
if ( ProcessEventIfMatchesId(*entry, handler, event) )
return true;
}
}
return false;
}
wxWidgets源碼分析(3) - 消息映射表