zoukankan      html  css  js  c++  java
  • duilib学习领悟(2)

      再次强调,duilib只不过是一种思想!

      在上一节中,我剖析了duilib中窗口类的注册,其中遗留两个小问题没有细说的?

      第一个问题:过程函数中__WndProc()中有这么一小段代码:

            pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
            if( uMsg == WM_NCDESTROY && pThis != NULL ) {
                LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
                if( pThis->m_bSubclassed ) pThis->Unsubclass();
                pThis->m_hWnd = NULL;
                pThis->OnFinalMessage(hWnd);
                return lRes;
            }

      有这么一段代码,请看我分析一下流程,首先第一句,从USER_DATA中取出this指针,接着判断是否是WM_NCDESTROY消息,这段有什么用呢?众所周知,这个WM_NCDESTROY消息是Windows程序退出时所发送的最后一个消息,那么拦截这个消息就是留给我们一个清场的机会,看到pThis->OnFinalMessage(hWnd)没?这个函数就是个虚函数,我们可以重写它,从而处理程序退出时应该做的后续工作,比如资源释放等.

      第二个问题:就是我上一节提到的duilib另外一套处理消息的机制

      接着看过程函数__WndProc()的代码:

      

    LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CWindowWnd* pThis = NULL;
        if( uMsg == WM_NCCREATE ) {
            LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
            pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
            pThis->m_hWnd = hWnd;
            ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
        } 
        else {
            pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
            if( uMsg == WM_NCDESTROY && pThis != NULL ) {
                LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
                if( pThis->m_bSubclassed ) pThis->Unsubclass();
                pThis->m_hWnd = NULL;
                pThis->OnFinalMessage(hWnd);
                return lRes;
            }
        }
        if( pThis != NULL ) {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        } 
        else {
            return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }

      请看红色标记部分.

      我之所以说,duilib采用的消息处理机制,和MFC稍微有些不同,是因为duilib的消息处理是在另一个类当中处理的,CPaintManagerUI 就是这个类!

      那么duilib为什么要设计这样一个类来处理消息, 消息处理为什么不放在CWindowWnd类中直接处理,而要放在CPaintManagerUI类中?这些归功于duilib设计者们的良苦用心!为什么?

      如果我们在不看duilib源码的基础上,也去设计一个这样的duilib,也许我们会这样设计:我们对消息的处理是在用户构建的类CDuiFrameWnd类(派生自CWindowWnd),为了在控件中可以实时刷新,那么每个控件都必须强制的保存一个变量---m_hWnd(主窗口句柄),而且,我们在每次处理消息时,都必然做一些重复性的工作(代码),那么我们将这些控件都具有的变量与操作都封装起来,这就是CPaintManagerUI类的由来!当然从名字看来,它主要处理的是绘图方面的消息,因为整个应用程序只有一个句柄(主窗口句柄),所以CPaintManagerUI作为管理者,承载了很多.看代码:

    class CDuiFrameWnd : public CWindowWnd, public INotifyUI
    {
    public:
        virtual LPCTSTR GetWindowClassName() const 
        { 
            return _T("DUIMainFrame"); 
        }
    
        virtual void    Notify(TNotifyUI& msg) 
        {
            if (msg.sType == _T("click"))
            {
                if (msg.pSender->GetName() == _T("btnClick"))
                {
                    ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
                }
            }
        }
    
        virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
        {
            LRESULT lRes = 0;
    
            if( uMsg == WM_CREATE ) 
            {
                CControlUI *pWnd = new CButtonUI;
                pWnd->SetName(_T("btnClick"));
                pWnd->SetText(_T("My First DUI"));   // 设置文字
                pWnd->SetBkColor(0xFF808080);       // 设置背景色
    
                m_PaintManager.Init(m_hWnd);        //主窗口句柄
                m_PaintManager.AttachDialog(pWnd);
                m_PaintManager.AddNotifier(this);
                return lRes;
            }
    
            if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) ) 
            {
                return lRes;
            }
    
            return __super::HandleMessage(uMsg, wParam, lParam);
        }
    
    protected:
        CPaintManagerUI m_PaintManager;
    };

      这就是我们的CDuiFrameWnd类,其中就有类CPaintManagerUI的对象m_PaintManager. 红色标记部分就是m_PaintManager处理绘图消息.如果没有被处理的其他消息,则给基类处理;

    不过CPaintManager为我们处理了最重要的两个消息:命令消息 和 通知消息 ,请看代码:

      

        case WM_NOTIFY:
            {
                LPNMHDR lpNMHDR = (LPNMHDR) lParam;
                if( lpNMHDR != NULL ) lRes = ::SendMessage(lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam);
                return true;
            }
            break;
        case WM_COMMAND:
            {
                if( lParam == 0 ) break;
                HWND hWndChild = (HWND) lParam;
                lRes = ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
                return true;
            }

      从代码上看,SendMessage()中的参数和MFC源码中是如出一辙,没什么两样,话不多说,上图,正所谓源码面前,了无秘语--j.j.hou

    LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
        // OnWndMsg does most of the work, except for DefWindowProc call
        LRESULT lResult = 0;
        if (!OnWndMsg(message, wParam, lParam, &lResult))
            lResult = DefWindowProc(message, wParam, lParam);
        return lResult;
    }
    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
        LRESULT lResult = 0;
        union MessageMapFunctions mmf;
        mmf.pfn = 0;
        CInternalGlobalLock winMsgLock;
        // special case for commands
        if (message == WM_COMMAND)
        {
            if (OnCommand(wParam, lParam))
            {
                lResult = 1;
                goto LReturnTrue;
            }
            return FALSE;
        }
    
        // special case for notifies
        if (message == WM_NOTIFY)
        {
            NMHDR* pNMHDR = (NMHDR*)lParam;
            if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
                goto LReturnTrue;
            return FALSE;
        }
    ...//代码太多,请自己查看余下的部分,这里只挑出我要讲的部分,代码在wincore.cpp中
    }

     接着看

    BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
        // return TRUE if command invocation was attempted
    {
        UINT nID = LOWORD(wParam);
        HWND hWndCtrl = (HWND)lParam;
        int nCode = HIWORD(wParam);
    
        // default routing for command messages (through closure table)
    
        if (hWndCtrl == NULL)
        {
            // zero IDs for normal commands are not allowed
            if (nID == 0)
                return FALSE;
    
            // make sure command has not become disabled before routing
            CTestCmdUI state;
            state.m_nID = nID;
            OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);
            if (!state.m_bEnabled)
            {
                TRACE(traceAppMsg, 0, _T("Warning: not executing disabled command %d
    "), nID);
                return TRUE;
            }
    
            // menu or accelerator
            nCode = CN_COMMAND;
        }
        else //请注意这里的控件反射消息
        {
            // control notification
            ASSERT(nID == 0 || ::IsWindow(hWndCtrl));
    
            if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
                return TRUE;        // locked out - ignore control notification
    
            // reflect notification to child window control
            if (ReflectLastMsg(hWndCtrl))
                return TRUE;    // eaten by child
    
            // zero IDs for normal commands are not allowed
            if (nID == 0)
                return FALSE;
        }
    
    #ifdef _DEBUG
        if (nCode < 0 && nCode != (int)0x8000)
            TRACE(traceAppMsg, 0, _T("Implementation Warning: control notification = $%X.
    "),
                nCode);
    #endif
    
        return OnCmdMsg(nID, nCode, NULL, NULL);
    }

     再看通知消息的处理

    BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
    {
        ASSERT(pResult != NULL);
        NMHDR* pNMHDR = (NMHDR*)lParam;
        HWND hWndCtrl = pNMHDR->hwndFrom;
    
        // get the child ID from the window itself
        UINT_PTR nID = _AfxGetDlgCtrlID(hWndCtrl);
        int nCode = pNMHDR->code;
    
        ASSERT(hWndCtrl != NULL);
        ASSERT(::IsWindow(hWndCtrl));
    
        if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
            return TRUE;        // locked out - ignore control notification
    
        // reflect notification to child window control
        if (ReflectLastMsg(hWndCtrl, pResult))
            return TRUE;        // eaten by child
    
        AFX_NOTIFY notify;
        notify.pResult = pResult;
        notify.pNMHDR = pNMHDR;
        return OnCmdMsg((UINT)nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL);
    }

       我复制了这么多代码干嘛?我想让大家引起注意的是我用蓝色加粗部分的ReflectLastMsg(hWndCtrl, pResult)函数,别着急,接着往下看:

    BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
    {
        // get the map, and if no map, then this message does not need reflection
        CHandleMap* pMap = afxMapHWND();
        if (pMap == NULL)
            return FALSE;
    
        // check if in permanent map, if it is reflect it (could be OLE control)
        CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild);
        ASSERT(pWnd == NULL || pWnd->m_hWnd == hWndChild);
        if (pWnd == NULL)
        {
    #ifndef _AFX_NO_OCC_SUPPORT
            // check if the window is an OLE control
            CWnd* pWndParent = (CWnd*)pMap->LookupPermanent(::GetParent(hWndChild));
            if (pWndParent != NULL && pWndParent->m_pCtrlCont != NULL)
            {
                // If a matching control site exists, it's an OLE control
                COleControlSite* pSite = (COleControlSite*)pWndParent->
                    m_pCtrlCont->m_siteMap.GetValueAt(hWndChild);
                if (pSite != NULL)
                {
                    CWnd wndTemp(hWndChild);
                    wndTemp.m_pCtrlSite = pSite;
                    LRESULT lResult = wndTemp.SendChildNotifyLastMsg(pResult);
                    wndTemp.m_hWnd = NULL;
                    return lResult != 0;
                }
            }
    #endif //!_AFX_NO_OCC_SUPPORT
            return FALSE;
        }
    
        // only OLE controls and permanent windows will get reflected msgs
        ASSERT(pWnd != NULL);
        return pWnd->SendChildNotifyLastMsg(pResult);
    }
    BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
    {
        _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
        return OnChildNotify(pThreadState->m_lastSentMsg.message,
            pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
    }
    BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    #ifndef _AFX_NO_OCC_SUPPORT
        if (m_pCtrlSite != NULL)
        {
            // first forward raw OCM_ messages to OLE control sources
            LRESULT lResult = SendMessage(OCM__BASE+uMsg, wParam, lParam);
            if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC &&
                (HBRUSH)lResult == NULL)
            {
                // for WM_CTLCOLOR msgs, returning NULL implies continue routing
                return FALSE;
            }
            if (pResult != NULL)
                *pResult = lResult;
            return TRUE;
        }
    #endif
    
        return ReflectChildNotify(uMsg, wParam, lParam, pResult);
    }
    BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
        // Note: reflected messages are send directly to CWnd::OnWndMsg
        //  and CWnd::OnCmdMsg for speed and because these messages are not
        //  routed by normal OnCmdMsg routing (they are only dispatched)
    
        switch (uMsg)
        {
        // normal messages (just wParam, lParam through OnWndMsg)
        case WM_HSCROLL:
        case WM_VSCROLL:
    #ifndef _WIN32_WCE
        case WM_PARENTNOTIFY:
    #endif // !_WIN32_WCE
        case WM_DRAWITEM:
        case WM_MEASUREITEM:
        case WM_DELETEITEM:
        case WM_VKEYTOITEM:
        case WM_CHARTOITEM:
        case WM_COMPAREITEM:
            // reflect the message through the message map as WM_REFLECT_BASE+uMsg
            return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult);
    
        // special case for WM_COMMAND
        case WM_COMMAND:
            {
                // reflect the message through the message map as OCM_COMMAND
                int nCode = HIWORD(wParam);
                if (CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))
                {
                    if (pResult != NULL)
                        *pResult = 1;
                    return TRUE;
                }
            }
            break;
    
        // special case for WM_NOTIFY
        case WM_NOTIFY:
            {
                // reflect the message through the message map as OCM_NOTIFY
                NMHDR* pNMHDR = (NMHDR*)lParam;
                int nCode = pNMHDR->code;
                AFX_NOTIFY notify;
                notify.pResult = pResult;
                notify.pNMHDR = pNMHDR;
                return CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), &notify, NULL);
            }
    
        // other special cases (WM_CTLCOLOR family)
        default:
            if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
            {
                // fill in special struct for compatiblity with 16-bit WM_CTLCOLOR
                AFX_CTLCOLOR ctl;
                ctl.hDC = (HDC)wParam;
                ctl.nCtlType = uMsg - WM_CTLCOLORMSGBOX;
                //ASSERT(ctl.nCtlType >= CTLCOLOR_MSGBOX);
                ASSERT(ctl.nCtlType <= CTLCOLOR_STATIC);
    
                // reflect the message through the message map as OCM_CTLCOLOR
                BOOL bResult = CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, 0, (LPARAM)&ctl, pResult);
                if ((HBRUSH)*pResult == NULL)
                    bResult = FALSE;
                return bResult;
            }
            break;
        }
    
        return FALSE;   // let the parent handle it
    }

       好了,我们终于到了最核心的部分,看到我用红色标记的部分没,这两个case就是WM_COMMAND和WM_NOTIFY消息的反射处理,我们可以看到MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY)..是不是和duilib的如出一辙?

      这些消息最终会流向基类CCmdTarget去查询消息映射表!

      反射消息处理路线,暂时先到这吧,东拉西扯了这么多,呵呵!

  • 相关阅读:
    微信浏览器取消缓存的方法
    iphone safari浏览器CSS兼容性的解决方案集合
    配置iis支持.json格式的文件
    win7下使用IIS服务器及自定义服务器端包含模块(SSI)步骤
    前端组件库集合
    ClientValidationFunction
    java 查询solr时间格式
    为何大量网站不能抓取?爬虫突破封禁的6种常见方法
    反爬虫四个基本策略
    ScheduledExecutorService 定时器用法
  • 原文地址:https://www.cnblogs.com/xiejiulong/p/3792671.html
Copyright © 2011-2022 走看看