鼠标点击了一个按钮之后,是怎样触发到对应的按钮的事件的?
首先,鼠标点击触发一系列Windows消息,这里以WM_LBUTTONUP举例说明消息处理过程:
首先,windows消息最终会到CPaintManagerUI::MessageHandler当中,中间的过程已经有很多文章讲述过,此处忽略不写;
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消息的处理,其实基本上所有鼠标消息的处理都是这个形式,先取出坐标值,然后找到对应的控件,最后封装成Duilib中的事件。那么显然关键就在于FindControl的实现:
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当中需要传入一个函数,这样就可以根据不同的寻找策略传入不同的函数,这里传入的是__FindControlFromPoint,显然是根据鼠标位置来寻找控件,此外Duilib中还有FindControlFromNameHash、FindControlFromTab、FindControlFromeShortCut等等,这里就不说了;
这段代码是不是好像很浅显,判断是否只查找visible跟enable的并判断自身状态,判断是否允许鼠标事件,判断传入的点是否属于自己的区域范围内,等等;
很明显,就这样是没有办法从层层嵌套的控件中找到最终响应事件的那一个的,真正的核心逻辑在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;
}
}
跟之前一样的判断,是否可见,可响应事件;对于控件容器来说多了个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 ) return pResult;
if( (uFlags & UIFIND_ME_FIRST) != 0 ) {
CControlUI* pControl = CControlUI::FindControl(Proc, pData, uFlags);
if( pControl != NULL ) return pControl;
}
常规的处理,判断是否在滚动条上,判断是否需要直接返回最外层容器,这样在部分场合就省去了对子元素的寻找;
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;
最后,确定了不在滚动条区域内,将该容器去掉滚动条区域与内边距,由for循环匹配子控件,两段循环其实逻辑一样,只是寻找顺序有区别而已。首先会进到子控件的FindControl中,匹配鼠标的点是否落在该控件,之后的判断是为了检查该点是否属于容器的内边距,因为控件布局后有可能有部分落在容器外面;假如最终没有符合条件的子控件,会走容器自己的CControlUI::FindControl,这时返回容器本身。
经过这一系列逻辑,即完成从一个点得到控件的操作。可以看出其实也没什么难的,就是拿子控件的矩形区域一个个去匹配鼠标的点而已,匹配不到则再判断是否为根节点,比较细节的地方就是滚动条与内边距的处理而已。