DuiLib事件分析(一)——滑鼠事件響應

來源:互聯網
上載者:User

標籤: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是無法預知,也不應該知道使用者層面想要的特效的。所以最終要解決這個問題,還是應該放到使用者訊息迴圈,通過比較複雜的邏輯去實現。

  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.