1. 程式人生 > >wxWidgets源碼分析(3) - 消息映射表

wxWidgets源碼分析(3) - 消息映射表

增加 iter size_t amp 進行 但是 刪除 rtu trie

目錄

  • 消息映射表
    • 靜態消息映射表
    • 靜態消息映射表處理過程
    • 動態消息映射表
    • 動態消息映射表處理過程

消息映射表

消息是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_TABLEEND_EVENT_TABLE之間:

  1. sm_eventTableEntries保存消息映射表,也就是消息ID和消息處理函數關系;
  2. 實現GetEventTable方法供調用者使用;
  3. 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_TABLEEND_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方法:

  1. wxEventHashTable根據消息類型將Table分段,這樣可以快速查找到指定類型的table;
  2. 遍歷指定類型的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處理流程如下:

  1. 檢查當前收到的消息是否與本項wxEventTableEntryBase匹配
  2. 調用用戶的處理函數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::DoBindwxEvtHandler::DoUnbind執行的,wxWidgets提供了多種接口執行這個操作。

需要註意的是:如果傳遞進wxEvtHandler對象,則在處理的時候調用用戶指定的wxEvtHandler對象進行處理;如果未指定,則直接使用當前的wxEvtHandler對象進行處理。

提供類似功能的還有Connect接口,但是Connect用起來比較復雜,建議後續代碼全部使用Bind接口,這裏不做贅述。

Bind接口是模板函數,提供三種接口:

  1. 直接綁定全局函數,此時只有event類型和function參數必須,建議使用
  2. 綁定Funtor函數,用戶需要自己用wxNewEventFunctor進行封裝,不建議使用
  3. 使用類內部函數,同時需要指定類指針,建議使用

三種接口聲明如下:

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) - 消息映射表