標籤:style blog http color 使用 os ar 2014
最近在處理DuiLib中自訂欄表行元素事件,因為處理方案得不到較好的效果,於是只好一層一層的去剝離DuiLib事件是怎麼來的,看能否在某一層截取訊息,自己重寫。
我這裡使用CListContainerElementUI行元素,元素中有插入button,平時行元素不顯示,滑鼠移動上去顯示出來,滑鼠移走就隱藏button。Duilib自己是不帶這個功能的,它有一個滑鼠移動上去的熱時間點事件,按理說重寫熱時間點事件就好了。但是當時比較急沒找到怎麼觸發的,之後一直沒繼續走這條思路。後來找到源碼事件裡面有
void CListContainerElementUI::DoEvent(TEventUI& event)if( event.Type == UIEVENT_MOUSEENTER ){ if( IsEnabled() ) { m_uButtonState |= UISTATE_HOT; Invalidate();
//我自己添加的回呼函數
pCallBackFunc(this);
} return;}
於是就在UIEVENT_MOUSEENTER中加入了自己寫的一個回呼函數,只要行元素觸發滑鼠移動上去的事件,就去調用回調,回調再去根據商務邏輯操作UI。想法很好,但是遇到一個問題,當滑鼠移動到行元素的button上時,行元素會觸發
if( event.Type == UIEVENT_MOUSELEAVE ){ if( (m_uButtonState & UISTATE_HOT) != 0 ) { m_uButtonState &= ~UISTATE_HOT; Invalidate(); } return;}
所以,如果在UIEVENT_MOUSEENTER時去設定為移動上行元素效果,在UIEVENT_MOUSELEAVE時設定為離開行元素效果,行不通。
當時設定了一個偷懶的方式,當滑鼠移動到另外一個行元素上時,重設其他行元素的UI為滑鼠離開。看似這樣就解決問題了,但是當滑鼠不移動到其他行元素上,直接離開列表時,則會導致滑鼠最後停留的那個行元素不能被置為滑鼠離開狀態。
通過對相關源碼的閱讀,得到以下關係圖:
而CControlUI並不繼承自任何類。
class UILIB_API CControlUI{public: CControlUI(); virtual ~CControlUI();......}
同時,在CControlUI中,DoEvent是來自於以下調用
void CControlUI::Event(TEventUI& event){ if( OnEvent(&event) ) DoEvent(event);}
查看Event引用得到:
前面提到CControlUI不繼承自任何類,顯然Event不是重構了一個系統函數,調用必然不會來自於的後面4條,而1,2條是定義和實現。這條線索算是中斷了。
於是,現在想從行元素滑鼠離開事件本身著手,看能否從堆疊追蹤裡面得到答案。在UIEVENT_MOUSELEAVE處斷點,跟蹤堆棧:
這樣我們就看到了是來自於以下調用
> dclient.exe!DuiLib::CPaintManagerUI::MessageHandler(unsigned int uMsg, unsigned int wParam, long lParam, long & lRes) 行 843 + 0x21 位元組 C++
我們再來看看MessangeHandler的代碼
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes){ ...... switch( uMsg ) { ..... case WM_CLOSE:... case WM_SIZE:... case WM_MOUSEHOVER:... case WM_MOUSELEAVE:... case WM_MOUSEMOVE:... ...... }
也就是說,訊息會在這裡被預設處理,而它的再上一層調用是使用者自己定義的訊息處理機制。我們此時再來看自己寫的調用:
//訊息迴圈LRESULT MyFrameWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){switch(uMsg){ ......case WM_CLOSE:... ......default:break;}if( bHandled ) return lRes;return WindowImplBase::HandleMessage(uMsg, wParam, lParam);}
這裡的return就是把訊息返回給了WindowImplBase
官方範例文件Duilib入門是返回給了
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
這兩個有些差異,但是並不影響我們繼續分析,我們的WindowImpIBase的調用就是來自於
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ CWindowWnd* pThis = NULL; ...... pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA)) ..... if( pThis != NULL ) { return pThis->HandleMessage(uMsg, wParam, lParam); } else { return ::DefWindowProc(hWnd, uMsg, wParam, lParam); }}
可見所有事件是來自於CWindowWnd視窗,視窗直接把事件給了開發人員,當開發人員不攔截相應事件的時候,就return給了DuiLib處理。DuiLib通過層層機制,反饋給相應的控制項去處理事件。
所有,要實現自訂訊息處理,不用動源碼,只要直接在HandleMessage中攔截訊息即可。
OK,看似思路清晰了,但是還面臨一個問題,給開發人員調用的事件裡面,沒有參數表明是哪個元素觸發的,想從這個層面攔截,似乎只能攔截一些通用事件,比如關閉,比如按鈕點擊。
我們來看看UIManager中,MessageHandler在處理滑鼠移動時的處理方法 case WM_MOUSEMOVE:
if( !m_bMouseTracking ) { TRACKMOUSEEVENT tme = { 0 }; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = m_hWndPaint; tme.dwHoverTime = m_hwndTooltip == NULL ? 400UL : (DWORD) ::SendMessage(m_hwndTooltip, TTM_GETDELAYTIME, TTDT_INITIAL, 0L); _TrackMouseEvent(&tme); m_bMouseTracking = true; } // Generate the appropriate mouse messages
//擷取滑鼠當前位置 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_ptLastMousePos = pt;
//根據位置擷取滑鼠在某個元素上 CControlUI* pNewHover = FindControl(pt); if( pNewHover != NULL && pNewHover->GetManager() != this ) break; TEventUI event = { 0 }; event.ptMouse = pt; event.dwTimestamp = ::GetTickCount();
//判斷是否滑鼠離開某個元素 if( pNewHover != m_pEventHover && m_pEventHover != NULL ) { event.Type = UIEVENT_MOUSELEAVE; event.pSender = m_pEventHover;
//這裡的m_pEventHover經過後面一些代碼運作,就會被賦值為pNewHover,也就是滑鼠移動上去的那個元素 m_pEventHover->Event(event); m_pEventHover = NULL; if( m_hwndTooltip != NULL ) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM) &m_ToolTip); }
//判斷滑鼠是否進入某個元素 if( pNewHover != m_pEventHover && pNewHover != NULL ) { event.Type = UIEVENT_MOUSEENTER; event.pSender = pNewHover; pNewHover->Event(event);
//設定熱點元素為當前元素 m_pEventHover = pNewHover; } if( m_pEventClick != NULL ) { event.Type = UIEVENT_MOUSEMOVE; event.pSender = m_pEventClick; m_pEventClick->Event(event); } else if( pNewHover != NULL ) { event.Type = UIEVENT_MOUSEMOVE;
event.pSender = pNewHover; pNewHover->Event(event); } }
先通過滑鼠位置判斷是否在某個元素上,在元素上則標記m_pEventHover為當前元素,再去配置相關訊息,調用元素的Event。也就是說,在DuiLib處理滑鼠移動事件時,根據滑鼠位置擷取了相應元素,並通觸發該元素行為。由此可見,自訂欄表行元素,當滑鼠停留在行元素上時,會觸發UIEVENT_MOUSEENTER,而當滑鼠移動到行元素的button上時,會修改m_pEventHover,它會認為這是移動到另外一個元素上了,去調用這個新元素的Event。
到我們可以整理出DuiLib滑鼠事件響應的大概流程:
1.表單觸發事件
2.使用者訊息迴圈,把處理不掉的資訊反饋給DuiLib的基類處理,基類通過CPaintManagerUI的MessageHandler來處理一些基礎訊息
3.MessageHandler把訊息分類處理
根據以上分析,如果要實現我前面提到的滑鼠移動上行元素,再改變行元素狀態,要通過修改DuiLib源碼來實現就非常困難了。MessageHandler是無法預知,也不應該知道使用者層面想要的特效的。所以最終要解決這個問題,還是應該放到使用者訊息迴圈,通過比較複雜的邏輯去實現。