Duilib源码解析——控件寻找

鼠标点击了一个按钮之后,是怎样触发到对应的按钮的事件的?
首先,鼠标点击触发一系列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,这时返回容器本身。
经过这一系列逻辑,即完成从一个点得到控件的操作。可以看出其实也没什么难的,就是拿子控件的矩形区域一个个去匹配鼠标的点而已,匹配不到则再判断是否为根节点,比较细节的地方就是滚动条与内边距的处理而已。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,473评论 1 11
  • 1、窗体 1、常用属性 (1)Name属性:用来获取或设置窗体的名称,在应用程序中可通过Name属性来引用窗体。 ...
    Moment__格调阅读 4,495评论 0 11
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,085评论 1 32
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,357评论 0 17
  • 在厦门学习的最后一天,我参加了一场演讲比赛。如果把这场演讲比赛比作“华山论剑”,那演讲中一招一式的铺排设计都是满满...
    郑腔宛调阅读 475评论 0 1