鼠標(biāo)點擊了一個按鈕之后,是怎樣觸發(fā)到對應(yīng)的按鈕的事件的够吩?
首先比然,鼠標(biāo)點擊觸發(fā)一系列Windows消息,這里以WM_LBUTTONUP舉例說明消息處理過程:
首先周循,windows消息最終會到CPaintManagerUI::MessageHandler當(dāng)中强法,中間的過程已經(jīng)有很多文章講述過,此處忽略不寫湾笛;
case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
//if( m_pEventClick == NULL ) break;
if (m_pEventClick == NULL) {
m_pEventClick = FindControl(pt);
if (m_pEventClick == NULL)
break;
}
ReleaseCapture();
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
m_pEventClick->Event(event);
m_pEventClick = NULL;
}
break;
這里是CPaintManagerUI:MessageHandler中對WM_LBUTTONUP消息的處理饮怯,其實基本上所有鼠標(biāo)消息的處理都是這個形式,先取出坐標(biāo)值嚎研,然后找到對應(yīng)的控件蓖墅,最后封裝成Duilib中的事件。那么顯然關(guān)鍵就在于FindControl的實現(xiàn):
CControlUI* CPaintManagerUI::FindControl(POINT pt) const
{
ASSERT(m_pRoot);
return m_pRoot->FindControl(__FindControlFromPoint, &pt,
UIFIND_VISIBLE | UIFIND_HITTEST | UIFIND_TOP_FIRST);
}
CControlUI* CALLBACK CPaintManagerUI::__FindControlFromPoint(CControlUI* pThis, LPVOID pData)
{
LPPOINT pPoint = static_cast<LPPOINT>(pData);
return ::PtInRect(&pThis->GetPos(), *pPoint) ? pThis : NULL;
}
CControlUI* CControlUI::FindControl(FINDCONTROLPROC Proc, LPVOID pData, UINT uFlags)
{
if( (uFlags & UIFIND_VISIBLE) != 0 && !IsVisible() ) return NULL;
if( (uFlags & UIFIND_ENABLED) != 0 && !IsEnabled() ) return NULL;
if( (uFlags & UIFIND_HITTEST) != 0 &&
(!m_bMouseEnabled || !::PtInRect(&m_rcItem, * static_cast<LPPOINT>(pData))) )
return NULL;
return Proc(this, pData);
}
顯然CControlUI::FindControl當(dāng)中需要傳入一個函數(shù)嘉赎,這樣就可以根據(jù)不同的尋找策略傳入不同的函數(shù)置媳,這里傳入的是__FindControlFromPoint,顯然是根據(jù)鼠標(biāo)位置來尋找控件公条,此外Duilib中還有FindControlFromNameHash拇囊、FindControlFromTab、FindControlFromeShortCut等等靶橱,這里就不說了寥袭;
這段代碼是不是好像很淺顯路捧,判斷是否只查找visible跟enable的并判斷自身狀態(tài),判斷是否允許鼠標(biāo)事件传黄,判斷傳入的點是否屬于自己的區(qū)域范圍內(nèi)杰扫,等等;
很明顯膘掰,就這樣是沒有辦法從層層嵌套的控件中找到最終響應(yīng)事件的那一個的章姓,真正的核心邏輯在Duilib中所有控件容器的基類,CContainerUI::FindControl中:
首先:
if( (uFlags & UIFIND_VISIBLE) != 0 && !IsVisible() ) return NULL;
if( (uFlags & UIFIND_ENABLED) != 0 && !IsEnabled() ) return NULL;
if( (uFlags & UIFIND_HITTEST) != 0 ) {
if( !::PtInRect(&m_rcItem, *(static_cast<LPPOINT>(pData))) ) return NULL;
if( !m_bMouseChildEnabled ) {
CControlUI* pResult = NULL;
if( m_pVerticalScrollBar != NULL ) pResult = m_pVerticalScrollBar->FindControl(Proc, pData, uFlags);
if( pResult == NULL && m_pHorizontalScrollBar != NULL ) pResult = m_pHorizontalScrollBar->FindControl(Proc, pData, uFlags);
if( pResult == NULL ) pResult = CControlUI::FindControl(Proc, pData, uFlags);
return pResult;
}
}
跟之前一樣的判斷识埋,是否可見凡伊,可響應(yīng)事件;對于控件容器來說多了個m_bMouseChildEnabled的標(biāo)志位窒舟,作用從名字就可以看出來系忙,假如禁止了子控件響應(yīng)鼠標(biāo)事件,這里還需要對滾動條做特殊處理惠豺,判斷鼠標(biāo)是否落在滾動條上银还;
CControlUI* pResult = NULL;
if( m_pVerticalScrollBar != NULL ) pResult = m_pVerticalScrollBar->FindControl(Proc, pData, uFlags);
if( pResult == NULL && m_pHorizontalScrollBar != NULL ) pResult = m_pHorizontalScrollBar->FindControl(Proc, pData, uFlags);
if( pResult != NULL ) return pResult;
if( (uFlags & UIFIND_ME_FIRST) != 0 ) {
CControlUI* pControl = CControlUI::FindControl(Proc, pData, uFlags);
if( pControl != NULL ) return pControl;
}
常規(guī)的處理,判斷是否在滾動條上洁墙,判斷是否需要直接返回最外層容器蛹疯,這樣在部分場合就省去了對子元素的尋找;
RECT rc = m_rcItem;
rc.left += m_rcInset.left;
rc.top += m_rcInset.top;
rc.right -= m_rcInset.right;
rc.bottom -= m_rcInset.bottom;
if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) rc.right -= m_pVerticalScrollBar->GetFixedWidth();
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();
if( (uFlags & UIFIND_TOP_FIRST) != 0 ) {
for( int it = m_items.GetSize() - 1; it >= 0; it-- ) {
CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindControl(Proc, pData, uFlags);
if( pControl != NULL ) {
if( (uFlags & UIFIND_HITTEST) != 0 && !pControl->IsFloat() && !::PtInRect(&rc, *(static_cast<LPPOINT>(pData))) )
continue;
else
return pControl;
}
}
}
else {
for( int it = 0; it < m_items.GetSize(); it++ ) {
CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindControl(Proc, pData, uFlags);
if( pControl != NULL ) {
if( (uFlags & UIFIND_HITTEST) != 0 && !pControl->IsFloat() && !::PtInRect(&rc, *(static_cast<LPPOINT>(pData))) )
continue;
else
return pControl;
}
}
}
if( pResult == NULL && (uFlags & UIFIND_ME_FIRST) == 0 ) pResult = CControlUI::FindControl(Proc, pData, uFlags);
return pResult;
最后热监,確定了不在滾動條區(qū)域內(nèi)苍苞,將該容器去掉滾動條區(qū)域與內(nèi)邊距,由for循環(huán)匹配子控件狼纬,兩段循環(huán)其實邏輯一樣羹呵,只是尋找順序有區(qū)別而已。首先會進(jìn)到子控件的FindControl中疗琉,匹配鼠標(biāo)的點是否落在該控件冈欢,之后的判斷是為了檢查該點是否屬于容器的內(nèi)邊距,因為控件布局后有可能有部分落在容器外面盈简;假如最終沒有符合條件的子控件凑耻,會走容器自己的CControlUI::FindControl,這時返回容器本身柠贤。
經(jīng)過這一系列邏輯香浩,即完成從一個點得到控件的操作【拭悖可以看出其實也沒什么難的邻吭,就是拿子控件的矩形區(qū)域一個個去匹配鼠標(biāo)的點而已,匹配不到則再判斷是否為根節(jié)點宴霸,比較細(xì)節(jié)的地方就是滾動條與內(nèi)邊距的處理而已囱晴。