zoukankan      html  css  js  c++  java
  • DuiLib事件分析(一)——鼠标事件响应

    最近在处理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是无法预知,也不应该知道用户层面想要的特效的。所以最终要解决这个问题,还是应该放到用户消息循环,通过比较复杂的逻辑去实现。

      

  • 相关阅读:
    NanUI for Winform发布,让Winform界面设计拥有无限可能
    使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞
    jQuery功能强大的图片查看器插件
    Entity Framework 5.0 Code First全面学习
    封装EF code first用存储过程的分页方法
    NET中使用Redis (二)
    Redis学习笔记~Redis主从服务器,读写分离
    ID3算法
    定性归纳(1)
    js加密
  • 原文地址:https://www.cnblogs.com/duguxue/p/3922686.html
Copyright © 2011-2022 走看看