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去查询消息映射表!

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

原文地址:https://www.cnblogs.com/xiejiulong/p/3792671.html