標籤:完成 ems with 重載 ble 繼承方式 fir item ati
目錄
- 訊息處理過程
- 訊息如何到達wxWidgets
- Win32訊息與wxWidgets訊息的轉換
- 菜單訊息處理
- 訊息處理鏈(基於wxEvtHandler)
- 訊息處理鏈(基於wxWindow)
- 總結
訊息處理過程訊息如何到達wxWidgets
Windows程式有其自身啟動並執行一套規律,::SendMessage是MS提供的windows訊息發送介面,使用者調用這個介面後會進入到MS系統庫程式,此介面指定了目標HWND和訊息參數,Windows系統內部會尋找指定HWND,然後通過gapfnScSendMessage介面調用使用者的訊息處理函數。
所以我們每次看到訊息處理函數都是通過gapfnScSendMessage調用進入的。
Win32訊息與wxWidgets訊息的轉換
wxWidgets註冊視窗時同時指定了視窗處理函數wxWndProc,當收到訊息後系統會調用此函數來處理訊息。
處理過程如下:
- 調用
wxFindWinFromHandle根據當前訊息指定的HWND來尋找對應的wxWindow,如果沒有則需要與最近建立的一個視窗關聯起來。
- 接著調用視窗的
MSWWindowProc方法來進行訊息處理。
// Main window procLRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ wxWindowMSW *wnd = wxFindWinFromHandle(hWnd); // 關聯視窗 if ( !wnd && gs_winBeingCreated ) { wxAssociateWinWithHandle(hWnd, gs_winBeingCreated); wnd = gs_winBeingCreated; gs_winBeingCreated = NULL; wnd->SetHWND((WXHWND)hWnd); } LRESULT rc; if ( wnd && wxGUIEventLoop::AllowProcessing(wnd) ) rc = wnd->MSWWindowProc(message, wParam, lParam); else rc = ::DefWindowProc(hWnd, message, wParam, lParam); return rc;}
MSWWindowProc是在windows平台下特有的虛函數,對於Frame類來說就是wxFrame::MSWWindowProc,訊息根據Message類型來執行不同的函數:
- WM_CLOSE: 退出操作,直接調用
wxFrame::Close;
- WM_SIZE: 調用
wxFrame::HandleSize;
- WM_COMMAND: 需要特殊處理,注釋中寫的很清楚,wxWidgets提供了一套自己的機制來進行父視窗和子視窗之間的訊息調用,所以就不要再使用Win32提供的功能了。
- 如果自己沒有處理,則給父類
wxFrameBase::MSWWindowProc處理;
WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam){ WXLRESULT rc = 0; bool processed = false; switch ( message ) { case WM_CLOSE: processed = !Close(); break; case WM_SIZE: processed = HandleSize(LOWORD(lParam), HIWORD(lParam), wParam); break; case WM_COMMAND: { WORD id, cmd; WXHWND hwnd; UnpackCommand((WXWPARAM)wParam, (WXLPARAM)lParam, &id, &hwnd, &cmd); HandleCommand(id, cmd, (WXHWND)hwnd); processed = true; } break; } if ( !processed ) rc = wxFrameBase::MSWWindowProc(message, wParam, lParam); return rc;}
拿wxFrame::Close函數舉例,wxFrame直接使用父類的方法wxWindowBase::Close,內容就是構造wxWidgets的訊息類型,然後調用HandleWindowEvent方法進行訊息處理。
bool wxWindowBase::Close(bool force){ wxCloseEvent event(wxEVT_CLOSE_WINDOW, m_windowId); event.SetEventObject(this); event.SetCanVeto(!force); // return false if window wasn‘t closed because the application vetoed the // close event return HandleWindowEvent(event) && !event.GetVeto();}
對於WM_SIZE,走的路要多一些,調用關係如下,最終由wxWindowMSW::HandleSize處理,這裡會產生wxSizeEvent訊息,隨後將訊息遞交給HandleWindowEvent處理:
wxFrame::MSWWindowProc() -> wxTopLevelWindowMSW::MSWWindowProc() -> wxWindowMSW::MSWWindowProc() -> wxWindowMSW::MSWHandleMessage() -> wxWindowMSW::HandleSize()bool wxWindowMSW::HandleSize(int WXUNUSED(w), int WXUNUSED(h), WXUINT wParam){ switch ( wParam ) { case SIZE_RESTORED: wxSizeEvent event(GetSize(), m_windowId); event.SetEventObject(this); processed = HandleWindowEvent(event); }}
也就是不管是哪個訊息,最終還是轉換稱wxEvent訊息,然後調用當前視窗的HandleWindowEvent函數處理,要注意的是此時訊息已經是wxWidgets內部的訊息類型了。
菜單訊息處理
接下來我們驗證菜單訊息在wxWidgets中的處理,首先在wxWidgets工程中增加靜態訊息映射表,並實現相應的代碼:
const long ID_MenuUser = wxNewId();BEGIN_EVENT_TABLE(debugWXFrame,wxFrame) EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu) EVT_UPDATE_UI(ID_MenuUser, debugWXFrame::OnCheckMenuUI)END_EVENT_TABLE()void debugWXFrame::OnCheckMenu(wxCommandEvent& event) {}void debugWXFrame::OnCheckMenuUI(wxUpdateUIEvent& event) {}
我們接著看下Command訊息的處理,這個用的非常多,收到Command類型的訊息後調用HandleCommand處理,Frame類只處理工具列、菜單和加速鍵命令,實現過程:
- 調用
FindItemInMenuBar根據訊息中指定的ID來尋找對應的wxMenuItem,這個函數的實現就是擷取到當前wxFrame的MenuBar,然後迴圈查詢,菜單越多查詢的速度也就越慢;
- 找到MenuItem後則調用
wxFrameBase::ProcessCommand(mitem)繼續處理:
bool wxFrame::HandleCommand(WXWORD id, WXWORD cmd, WXHWND control){#if wxUSE_MENUS#if defined(WINCE_WITHOUT_COMMANDBAR) if (GetToolBar() && GetToolBar()->FindById(id)) return GetToolBar()->MSWCommand(cmd, id);#endif // we only need to handle the menu and accelerator commands from the items // of our menu bar, base wxWindow class already handles the rest if ( !control && (cmd == 0 /* menu */ || cmd == 1 /* accel */) ) {#if wxUSE_MENUS_NATIVE if ( !wxCurrentPopupMenu )#endif // wxUSE_MENUS_NATIVE { wxMenuItem * const mitem = FindItemInMenuBar((signed short)id); if ( mitem ) return ProcessCommand(mitem); } }#endif // wxUSE_MENUS return wxFrameBase::HandleCommand(id, cmd, control);;}
wxFrame類本身沒有實現ProcessCommand,所以將調用父類的方法wxFrameBase::ProcessCommand,關鍵流程代碼部分調用wxMenu的SendEvent函數繼續處理。
重點關注,這裡調用的是menu->SendEvent,所以接下來的調用切換到wxMenu類中進行。
bool wxFrameBase::ProcessCommand(wxMenuItem *item){ ... wxMenu* const menu = item->GetMenu(); return menu->SendEvent(item->GetId(), checked);}
wxMenu的SendEvent實現是wxMenuBase::SendEvent方法,此時我們位於wxMenu對象中,所以調用GetEventHandler()獲得的是wxMenu的EvntHandler。
重點關注win和mb兩個變數,wxMenu首先使用自己的wxEvtHandler進行處理,然後檢查它是否關聯到了win或者menubar,如果有則它還增加了一個標記event.SetWillBeProcessedAgain(),也就是命令需要被wen或者menubar處理。
win和mb兩個變數代表不同的菜單類型,mb是菜單條中的菜單,win是操作功能表。
這裡我們調用的是mb->HandleWindowEvent(event);
bool wxMenuBase::SendEvent(int itemid, int checked){ wxCommandEvent event(wxEVT_MENU, itemid); event.SetEventObject(this); event.SetInt(checked); wxWindow* const win = GetWindow(); wxMenuBar* const mb = GetMenuBar(); wxEvtHandler *handler = GetEventHandler(); if ( handler ) { if ( win || mb ) event.SetWillBeProcessedAgain(); // 沒有想到調用這個函數的情境? if ( handler->SafelyProcessEvent(event) ) return true; } // If this menu is part of the menu bar, process the event there: this will // also propagate it upwards to the window containing the menu bar. if ( mb ) return mb->HandleWindowEvent(event); // Try the window the menu was popped up from. if ( win ) return win->HandleWindowEvent(event); // Not processed. return false;}
至此,我們將切換到wxMenuBar::HandleWindowEvent,所有者為wxMenuBar,wxMenuBar繼承自wxWindow類,它也是一個獨立的視窗,所以這次調用的函數是wxWindowBase::HandleWindowEvent,調用過程如下:
GetEventHandler()方法返回的就是自身,wxFrame本身就是繼承自wxEvtHandler;
ProcessEvent方法是由父類wxEvtHandler提供的;
bool wxWindowBase::HandleWindowEvent(wxEvent& event) const{ return GetEventHandler()->SafelyProcessEvent(event);}bool wxEvtHandler::SafelyProcessEvent(wxEvent& event){ return ProcessEvent(event);}
接著調用ProcessEvent,這個函數是通用的,只要是繼承自wxEvtHandler都會調用到這裡,下面我們分兩種情況來說明情況:
通用的處理:
- 處理全域過濾器,
wxEvtHandler內部建立了靜態變數ms_filterList,用於儲存wxEventFilter列表,使用者可以通過調用靜態函數wxEvtHandler::AddFilter來向系統中增加過濾器,具體可參考過濾器使用章節;
- 調用
TryBeforeAndHere僅對本對象處理,調用此函數需要依賴一個標記ShouldProcessOnlyIn,這個標記僅僅在DoTryChain中會被設定,也就是只有進入了DoTryChain函數才會有此標記;
- 調用
ProcessEventLocally執行本對象處理;
- 調用
TryAfter執行parent的處理;
第一種情況: 位於wxMenuBar中的處理:
此時我們位於wxMenuBar中,此類繼承自wxEvtHandler,所以這裡調用的實際是wxEvtHandler::ProcessEvent,處理過程:
- 此時是不會有
ShouldProcessOnlyIn標記,所以不會執行TryBeforeAndHere;
- 進入到
ProcessEventLocally;由於wxMenuBar對象中並沒有綁定此菜單的處理函數,所以ProcessEventLocally是不會處理的;
- 進入到
TryAfter執行parent的處理;
第二種情況: 位於wxFrame中的處理:
對於wxFrame的ProcessEvent流程也有同樣的效果,只不過會在ProcessEventLocally中處理。
bool wxEvtHandler::ProcessEvent(wxEvent& event){ // 處理過濾器 if ( !event.WasProcessed() ) { for ( wxEventFilter* f = ms_filterList; f; f = f->m_next ) { int rc = f->FilterEvent(event); if ( rc != wxEventFilter::Event_Skip ) { return rc != wxEventFilter::Event_Ignore; } } } // 只有執行了 DoTryChain() 之後,ShouldProcessOnlyIn()方法才會返回true // 具體可以參考 wxEventProcessInHandlerOnly 輔助類 if ( event.ShouldProcessOnlyIn(this) ) return TryBeforeAndHere(event); // Try to process the event in this handler itself. if ( ProcessEventLocally(event) ) { return !event.GetSkipped(); } if ( TryAfter(event) ) return true; // No handler found anywhere, bail out. return false;}
還是分成兩種情況分別說明:
第一種情況: 位於wxMenuBar中的處理:
wxEvtHandler::TryBeforeAndHere會調用TryBefore||TryHereOnly,TryBefore我們暫時忽略,重點是TryHereOnly,在TryHereOnly函數中,首先超找動態綁定表,然後尋找靜態繫結表,如果表中存在處理函數則調用之,否則不會調用,對於本流程來將,wxMenubar中並沒有綁定任何處理函數,所以TryHereOnly返回false,進而TryBeforeAndHere函數返回false,所以需要繼續調用DoTryChain。
第二種情況: 位於wxFrame中的處理:
對於wxFrame來說,本例中菜單的訊息處理函數綁定在靜態繫結區,所以會在if ( GetEventHashTable().HandleEvent(event, this) )中處理掉,返回true。
bool wxEvtHandler::ProcessEventLocally(wxEvent& event){ return TryBeforeAndHere(event) || DoTryChain(event);}bool wxEvtHandler::TryBeforeAndHere(wxEvent& event){ return TryBefore(event) || TryHereOnly(event);}bool wxEvtHandler::TryHereOnly(wxEvent& event){ // Handle per-instance dynamic event tables first if ( m_dynamicEvents && SearchDynamicEventTable(event) ) return true; // Then static per-class event tables if ( GetEventHashTable().HandleEvent(event, this) ) return true; return false;}
繼續進入入wxMenuBar的DoTryChain,這裡是通過設定EventHandlerChain來達到訊息傳遞的目的,但是在wxWidgets系統中,wxWindow繼承的過程中明確不使用這種方式進行訊息傳遞,而是通過wxWindow自身的父子關係來進行訊息傳遞,所以對於wxMenuBar來說,這個GetNextHandler必定返回是空的,所以DoTryChain返回false,進而wxMenuBar的ProcessEventLocally返回false。
bool wxEvtHandler::DoTryChain(wxEvent& event){ for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() ) { wxEventProcessInHandlerOnly processInHandlerOnly(event, h); if ( h->ProcessEvent(event) ) { event.Skip(false); return true; } if ( !event.ShouldProcessOnlyIn(h) ) { event.Skip(); return true; } } return false;}
再回到前兩步,此時我們只能通過調用wxMenuBar的TryAfter(event)繼續訊息傳遞,前文有描述wxMenuBar繼承自wxWindows,所以這裡調用的是wxWindowBase::TryAfter,在下面的調用中,視窗只要可能正常接收訊息,則會向上尋找parent,然後調用父類的ProcessEvent繼續處理。
在本例中,wxMenuBar的parent是wxFrame,所以會繼續調用wxFrame的ProcessEvent繼續處理
bool wxWindowBase::TryAfter(wxEvent& event){ if ( event.ShouldPropagate() ) { if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) ) { wxWindow *parent = GetParent(); if ( parent && !parent->IsBeingDeleted() ) { wxPropagateOnce propagateOnce(event, this); return parent->GetEventHandler()->ProcessEvent(event); } } } return wxEvtHandler::TryAfter(event);}
wxFrame的ProcessEvent的調用順序與wxMenuBar的相同,只不過wxFrame會在ProcessEventLocally方法中返回true,進而導致整個處理流程完成。
訊息處理鏈(基於wxEvtHandler)
wxWidgets提供了一種手段,使用者可以將訊息處理函數注入到wxEvtHandler類中,而不需要使用繼承方式,實現方法就是使用者自訂一個wxEventHandler類,然後調用wxEvtHandler::SetNextHandler()將訊息處理代碼加入到指定的wxEvtHandler對象上,使用舉例:
下面的代碼用於將菜單處理放到獨立的wxEvtHandler類中,通過wxEvtHandler::SetNextHandler方法將此Handler對象連結到wxFrame上:
註:必須使用wxEvtHandler::SetNextHandler方法注入,不能直接調用SetNextHandler,因為wxWindow重載了這個方法,直接調用不會生效。
const long ID_MenuUser = wxNewId();class CMyEvtHandler : public wxEvtHandler {public: bool ProcessEvent(wxEvent& event) { if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser) { wxMessageBox("Menu processed in chain"); return true; } event.Skip(); return false; }};debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id){ ... Menu1->Append(ID_MenuUser, _("Chain Menu")); wxEvtHandler::SetNextHandler(new CMyEvtHandler);}
實際調用時,會進入到wxFram::DoTryChain函數中,由於我們向wxFrame中增加了wxEvtHandler,此時會取出關係鏈上的wxEvtHandler逐個調用。
注意到使用這種方式調用時會預先設定只在當前對象中處理標記,通過wxEventProcessInHandlerOnly processInHandlerOnly(event, h);實現,當processInHandlerOnly銷毀後標記消失,作用範圍僅僅是在這個迴圈體內。
bool wxEvtHandler::DoTryChain(wxEvent& event){ for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() ) { wxEventProcessInHandlerOnly processInHandlerOnly(event, h); if ( h->ProcessEvent(event) ) { event.Skip(false); return true; } if ( !event.ShouldProcessOnlyIn(h) ) { event.Skip(); return true; } } return false;}
訊息處理鏈(基於wxWindow)
除了將訊息處理類注入到當前wxEvtHandler對象中,還有一個辦法就是調用wxWindow::PushEventHandler將訊息處理類注入到當前windows的棧中,兩種方式有區別:
- 注入到
wxEvtHandler,wxWidgets會優先處理當前對象的wxEvtHandler,然後檢查當前對象的wxEvtHandler是否有連結其他的wxEvtHandler,如果有則調用之;
- 通過
wxWindow::PushEventHandler注入的是改寫wxWindow類的當前訊息處理對象,當其尋找wxWindow對象的訊息處理對象時,只調用最後插入的一個,所以,為了保證正常的訊息能處理,我們必須在ProcessEvent()方法中調用下一個wxEvtHandler的方法。
舉例:
const long ID_MenuUser_wxWinChain = wxNewId();class CMyWinEvtHandler : public wxEvtHandler {public: bool ProcessEvent(wxEvent& event) { if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser_wxWinChain) { wxMessageBox("Menu processed in chain, id="+wxString::Format("%d", event.GetId())); return true; } if (GetNextHandler()) return GetNextHandler()->ProcessEvent(event); return false; }};debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id){ ... Menu1->Append(ID_MenuUser_wxWinChain, _("Chain Menu")); PushEventHandler(new CMyWinEvtHandler);}
拿菜單處理舉例,在wxMenuBar調用wxWindowBase::TryAfter尋找父類調用時,會直接調用父類的方法,對於我們這個例子來說,會直接調用CMyWinEvtHandler::ProcessEvent方法,所以我們在實現ProcessEvent必須注意,需要調用GetNextHandler()->ProcessEvent(event)以保證其他訊息的正常處理。
if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) ){ wxWindow *parent = GetParent(); if ( parent && !parent->IsBeingDeleted() ) { wxPropagateOnce propagateOnce(event, this); return parent->GetEventHandler()->ProcessEvent(event); }}
註:在測試過程中發現總會有the last handler of the wxWindow stack should have this window as next handler的提示,這個是wxWidgets庫本身代碼的Bug,視窗鏈不需要雙向鏈表,視窗本身的wxEvtHandler不需要指向任何wxEvtHandler,因為它就是最後一個。
總結
本節中主要描述了wxWidgets的訊息處理過程。
wxWidgets源碼分析(4) - 訊息處理過程