zoukankan      html  css  js  c++  java
  • 转MFC消息映射梳理

    http://blog.csdn.net/phunxm/article/details/5640766

    一.CWnd消息处理

    一切从窗口(HWND)的创建说起,在MFC中,CWnd::CreateEx执行窗口创建过程。

    从调用BOOL CWnd::Attach(HWND hWndNew)那一刻起,即将一个窗口(HWND)托付给一个具体的CWnd对象(子类化)。

    BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

    {

        // allow modification of several common create parameters

        CREATESTRUCT cs;

        cs.dwExStyle = dwExStyle;

        cs.lpszClass = lpszClassName;

        cs.lpszName = lpszWindowName;

        cs.style = dwStyle;

        cs.x = x;

        cs.y = y;

        cs.cx = nWidth;

        cs.cy = nHeight;

        cs.hwndParent = hWndParent;

        cs.hMenu = nIDorHMenu;

        cs.hInstance = AfxGetInstanceHandle();

        cs.lpCreateParams = lpParam;

     

        if (!PreCreateWindow(cs))

        {

           PostNcDestroy();

           return FALSE;

        }

     

        AfxHookWindowCreate(this);

        HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

     

    #ifdef _DEBUG

        if (hWnd == NULL)

        {

           TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n", GetLastError());

        }

    #endif

     

        if (!AfxUnhookWindowCreate())

           PostNcDestroy();        // cleanup if CreateWindowEx fails too soon

     

        if (hWnd == NULL)

           return FALSE;

        ASSERT(hWnd == m_hWnd); // should have been set in send msg hook

        return TRUE;

    }

    在PreCreateWindow之后,::CreateWindowEx之前,AfxHookWindowCreate函数中::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());安装钩子。钩子函数_AfxCbtFilterHook中调用::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);将创建hWnd时所使用的WNDCLASS窗口模板的过程函数由DefWindowProc更改为AfxWndProc。

    // The WndProc for all CWnd's and derived classes

    LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

    {

    // ……

        // all other messages route through message map

        CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

    // ……

        return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

    }

    窗口函数AfxWndProc从AFX_MODULE_THREAD_STATE::m_pmapHWND中查询hWnd对应的CWnd对象,AfxCallWndProc将消息委托给具体窗口对象的WindowProc函数处理。

    LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)

    {

    // ……

           // delegate to object's WindowProc

           lResult = pWnd->WindowProc(nMsg, wParam, lParam);

    // ……

    }

    WindowProc函数调用OnWndMsg函数。

    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;

    }

    在OnWndMsg中,对消息进行分流处理:

    (1)WM_COMMANDàOnCommand

    // AFXMSG_.H

    #define ON_COMMAND(id, memberFxn) /

        { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },

    (2)WM_NOTIFYàOnNotify

    // AFXMSG_.H

    #define ON_NOTIFY(wNotifyCode, id, memberFxn) /

        { WM_NOTIFY, (WORD)(int)wNotifyCode, (WORD)id, (WORD)id, AfxSig_vNMHDRpl, /

           (AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)(NMHDR*, LRESULT*))&memberFxn },

    (3)WM_ACTIVATE消息特殊处理(special case for activation)

    (4)WM_SETCURSOR消息特殊处理(special case for set cursor HTERROR)

    (5)对于常规窗口消息,OnWndMsg调用GetMessageMap()函数获得DECLARE_MESSAGE_MAP声明的消息映射表AFX_MSGMAP,根据nMessage和nCode调用AfxFindMessageEntry函数从消息映射表中查找AFX_MSGMAP_ENTRY,根据函数签名nSig从MessageMapFunctions中查询正确的函数指针,完成消息函数(AFX_PMSG pfn)的正确调用。

    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

    {

        LRESULT lResult = 0;

     

        // special case for commands

        if (message == WM_COMMAND)

        {

           if (OnCommand(wParam, lParam)) // virtual

           {

               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)) // virtual

               goto LReturnTrue;

           return FALSE;

        }

     

        // special case for activation

        // special case for set cursor HTERROR

    const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();

    // ……

    LReturnTrue:

        if (pResult != NULL)

           *pResult = lResult;

        return TRUE;

    // ……

    }

    如果OnWndMsg函数对消息进行了处理,则返回TRUE,否则WindowProc中调用DefWindowProc对消息进行默认处理。

    二.WM_COMMAND消息和WM_NOTIFY消息

    消息是根据窗口进行分发的,而不考虑该窗口的从属关系。也就是说在子窗口中产生的消息只在子窗口函数中处理,处理完后不会再把消息传递给父窗口。父子窗口若要对某一事件做协同处理,需要做相应的通知消息反射

    class CWnd : public CCmdTarget

    {

    // ……

    protected:

        virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

        virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);

    // ……

    }

    OnCommand函数为对子窗口控件的WM_COMMAND命令通知消息的响应;OnNotify函数为对通用控件的WM_NOTIFY通知消息的响应。

    1.子窗口控件的WM_COMMAND消息

    1)子窗口控件的WM_COMMAND消息

    Windows自定义的子窗口控件(Predefined Chilld Window Controls)包括BUTTON、COMBOBOX、EDIT、LISTBOX、RichEdit、SCROLLBAR、STATIC。

    以上Windows预定义的子窗口控件的窗口过程中做了反射处理,即在某一事件(点击菜单,点击加速键,点击子窗口按钮,点击工具栏按钮)处理后发送一个WM_COMMAND通知消息给父窗口。这样某个事件发生了,子窗口做默认处理后,父窗口可以做后续处理使父子窗口工作协调。例如按下按钮,按钮呈凹陷状,这是由BtnProc对WM_LBUTTONDOWN(WM_PAINT)的重绘处理;松开按钮,按钮恢复原态,这是由BtnProc对WM_LBUTTONUP(WM_PAINT)的重绘处理。往往在松开按钮的时候,发送WM_COMMAND消息(消息码为BN_CLICKED)给父窗口,由父窗口做点击事件响应,这样便于状态和逻辑的分离。

    The BN_CLICKED notification code is sent when the user clicks a button.

    The parent window of the button receives the BN_CLICKED notification code through the WM_COMMAND message.                          ——MSDN

    预定义BUTTON窗口类的过程函数BtnProc的处理流程大概如下:

    LRESULT CALLBACK BtnProc(HWND hWnd, UINT nMessage, WPARAM wParam, LPARAM lParam)

    {

        switch (nMessage)

        {

        case WM_LBUTTONDOWN:

           {

               // repaint(凹陷)

               RECT rc;

               GetClientRect(hWnd, &rc);

               InvalidateRect(hWnd, rc, TRUE);

               // ……

           }

           break;

     

        case WM_LBUTTONUP:

            {

               // repaint(复原)

               RECT rc;

               GetClientRect(hWnd, &rc);

               InvalidateRect(hWnd, rc, TRUE);

     

               // notify parent click event(IDC_BTN在调用::CreateWindowEx时指定)

               // LONG IDC_BTN = GetWindowLong(hWnd, GWL_ID);

               PostMessage(GetParent(hWnd), WM_COMMAND, MAKEWPARAM(IDC_BTN, BN_CLICKED), (LPARAM)hWnd);

               // ……

           }

           break;

     

        // ……

     

        default:

           DefWindowProc(hWnd, nMessage, wParam, lParam);

        }

    }

    2WM_COMMAND消息的处理逻辑(子先父后)

    父窗口的OnCommand函数响应子窗口控件(Chilld Window Controls)发送来的命令消息,相当于传统窗口过程中对WM_COMMAND消息按ID(LOWORD(wParam))进行分类处理。其中HIWORD(wParam)为命令通知码,lParam参数为子窗口控件的句柄。

    // CWnd command handling

    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)

           {

               TRACE1("Warning: not executing disabled command %d/n", 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)

           TRACE1("Implementation Warning: control notification = $%X./n",

               nCode);

    #endif

     

        return OnCmdMsg(nID, nCode, NULL, NULL);

    }

    OnCommand中优先将消息交给子窗口控件自身处理ReflectLastMsg(reflect notification to child window control)。如果子控件做了处理,那么ReflectLastMsg返回TRUE,OnCommand返回TRUE,OnWndMsg返回TRUE,参数*pResult = lResult = 1。若子控件未做处理,则交由父窗口的OnCmdMsg函数处理,OnCmdMsg从父窗口消息映射表中查找相应通知消息的处理入口。

    ReflectLastMsg函数先查找到子窗口hWndChild对应的CWnd窗口对象,然后调用子窗口对象的SendChildNotifyLastMsg函数。

    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)

        {

    //!_AFX_NO_OCC_SUPPORT

        }

     

        // only OLE controls and permanent windows will get reflected msgs

        ASSERT(pWnd != NULL);

        return pWnd->SendChildNotifyLastMsg(pResult);

    }

        SendChildNotifyLastMsg函数调用OnChildNotify函数。

    BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)

    {

        _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();

        return OnChildNotify(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, ThreadState->m_lastSentMsg.lParam, pResult);

    }

    OnChildNotify函数调用ReflectChildNotify函数。

    BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

    {

        // first forward raw OCM_ messages to OLE control sources

       // ……

        return ReflectChildNotify(uMsg, wParam, lParam, pResult);

    }

    ReflectChildNotify函数调用OnCmdMsg。

    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)

        // ……

           // 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:

           // ……

           break;

        }

     

        return FALSE;   // let the parent handle it

    }

    如果子窗口OnCmdMsg做了处理(即子窗口的AFX_MSGMAP_ENTRY中有对该WM_COMMAND消息的处理),则ReflectChildNotify函数、OnChildNotify函数、SendChildNotifyLastMsg函数、ReflectLastMsg函数返回TRUE,*pResult = 1。

    如果子窗口OnCmdMsg未做处理(即子窗口的AFX_MSGMAP_ENTRY中没有对该WM_COMMAND消息的处理),则ReflectChildNotify函数、OnChildNotify函数、SendChildNotifyLastMsg函数、ReflectLastMsg函数返回FALSE,*pResult = 0。此时,父窗口的OnCommand中ReflectLastMsg(hWndCtrl)返回FALSE,消息流向父窗口的OnCmdMsg,在父窗口的AFX_MSGMAP_ENTRY中查找对该WM_COMMAND消息的处理。

    (3)WM_COMMAND消息的最终归宿(CWnd::OnCmdMsgàCCmdTarget::OnCmdMsgà_AfxDispatchCmdMsgàAfxFindMessageEntry)

    CWnd::OnCmdMsg是个继承自CCmdTarget的虚函数,由于CWnd并未对该函数进行覆写(CWnd的派生类CDialog、CFrameWnd、CView、CDocument等对其覆盖),故其最终流向CCmdTarget::OnCmdMsg,其主要完成消息路由,核心代码如下。

    BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)

    {

    // ……

    for (pMessageMap = GetMessageMap(); pMessageMap != NULL;

          pMessageMap = pMessageMap->pBaseMap)

    {

           lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);

           if (lpEntry != NULL)

           {

               // found it

               // ……

               return _AfxDispatchCmdMsg(this, nID, nCode,

                  lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);

           }

        }

        return FALSE;   // not handled

    }

    CCmdTarget::OnCmdMsg从当前窗口(this)至具备命令消息处理的终极归属CCmdTarget根据消息号(nID)和消息码(nCode)遍历查找相应通知消息的处理入口:

    for (;;) lpEntry = AfxFindMessageEntry(,,,);

    若找到了消息映射,则调用_AfxDispatchCmdMsg(this,,,,,,),其中根据函数签名(lpEntry->nSig),调用(this->*lpEntry->pfn)(*)完成最终的消息处理。注意,这里通过this既可以是对当前对象覆盖的调用,也可能是对直接从基类继承的调用。

    (4)常用子窗口控件的WM_COMMAND消息映射宏格式举例

    CWnd对菜单的WM_COMMAND通知消息的处理,直接使用ON_COMMAND (id, memberFxn)宏进行映射。对子窗口控件的WM_COMMAND通知消息按照控件ID和消息通知码,进行了分类定义。使用ON_CONTROL(wNotifyCode, id, memberFxn)宏映射一个函数到一个定制控件通知消息。其中,定制控件通知消息是从一个控件发送到其父窗口的消息。ON_COMMAND(id, OnFoo) is the same as ON_CONTROL(0, id, OnFoo)。

    例如对按钮通知消息BN(Button Notification),父窗口的消息映射中以宏ON_BN_CLICKED(id, memberFxn)来定义该窗口对按钮子控件的点击事件BN_CLICKED的响应为memberFxn。

    #define ON_BN_CLICKED(id, memberFxn) /

        ON_CONTROL(BN_CLICKED, id, memberFxn)

    类似的,对ComboBox的通知消息CBN(ComboBox Notification),父窗口的消息映射中以宏ON_CBN_SELCHANGE(id, memberFxn)来定义该窗口对ComboBox子控件的选项改变事件CBN_SELCHANGE的响应为memberFxn。

    #define ON_CBN_SELCHANGE(id, memberFxn) /

    ON_CONTROL(CBN_SELCHANGE, id, memberFxn)

    Static对应的通知消息STN(Static Notification),对应消息映射宏为ON_STN_*。

    #define ON_STN_CLICKED(id, memberFxn) /

    ON_CONTROL(STN_CLICKED, id, memberFxn)

    Edit对应的通知消息EN(Edit Notification),对应消息映射宏为ON_EN_*。

    #define ON_EN_SETFOCUS(id, memberFxn) /

        ON_CONTROL(EN_SETFOCUS, id, memberFxn)

    ListBox对应的通知消息LBN (ListBox Notification) ,对应消息映射宏为ON_LBN_*。

    #define ON_LBN_SELCANCEL(id, memberFxn) /

        ON_CONTROL(LBN_SELCANCEL, id, memberFxn)

    2.通用控件的WM_NOTIFY消息

    1)通用控件的WM_NOTIFY消息处理流程

    常用的通用控件(Common Controls)包括ToolBar、Tooltip、Status Bar、Tree View、List View、Animation、Header、Hot-Key、Progress Bar、Up-down、Tab等,它们是增强型子窗口控件,由comctrl32.dll实现。

    OnNotify函数完成通用控件的通知消息处理,相当于传统窗口过程中对WM_NOTIFY消息按ID(identifier)、消息码(notification code)进行处理。一般LOWORD(wParam)为控件ID,HIWORD(wParam)为通知码(notification code),lParam参数为NMHDR*(Notify Message HeaDeR)。

    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 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(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL);

    }

    OnNotify中优先将消息交给通用控件自身处理ReflectLastMsg(reflect notification to child window control)。如果子控件做了处理,那么ReflectLastMsg返回TRUE,OnCommand返回TRUE,OnWndMsg返回TRUE,参数*pResult = lResult = 1。若子控件未做处理,则交由父窗口的OnCmdMsg函数处理,OnCmdMsg从父窗口消息映射表中查找相应通知消息的处理入口。

    ReflectLastMsg后面的调用流程同OnCommand。

    (2)常用通用控件的WM_NOTIFY消息映射宏格式举例

    对通用控件的WM_NOTIFY消息,直接使用ON_NOTIFY(wNotifyCode, id, memberFxn)对ID号为id的通知码为wNotifyCode的通知消息进行映射。

    例如对于树控件IDC_TREECTRL树的选项改变事件(TVN_SELCHANGED)的消息映射如下:

    ON_NOTIFY(TVN_SELCHANGED, IDC_TREECTRL, OnSelChangedTreeCtrl)

    (3)通用控件的WM_NOTIFY消息反射

    ON_NOTIFY不反射消息。如果自己处理不了,就传给上级窗口;如果再处理不了,再往上传。实在处理不了,由框架默认处理。

    ON_NOTIFY_REFLECT反射消息,把消息传给上级窗口处理,如果上级都处理不了,再反射回来自己处理。这就是MFC强大的消息反射机制。

    ON_NOTIFY_REFLECT是处理消息的手段,控件传递给父窗体的普通的消息都是由父窗体函数来处理。但由ON_NOTIFY_REFLECT映射的消息先由该控件处理,如果该控件没有处理函数再发往父窗体处理。

    ON_NOTIFY_REFLECT_EX映射的处理函数(OnNotify),如果返回值(LRESULT* pResult)为1,则父窗体不进行处理;如果返回值为0,则控件处理完后,父窗体也进行处理。

  • 相关阅读:
    [NOI2009]管道取珠 DP + 递推
    poj3207 Ikki's Story IV
    NOIP2016Day1T2天天爱跑步(LCA+桶)
    NOIP2016Day2T3愤怒的小鸟(状压dp) O(2^n*n^2)再优化
    NOIP2016Day1T3换教室(floyd+期望dp)
    bzoj1854: [Scoi2010]游戏(匈牙利) / GDKOI Day2 T2(最大流)
    [CodeVs4927]线段树练习5
    基数排序的奇技淫巧
    bzoj2724: [Violet 6]蒲公英(离散化+分块)
    bzoj1483: [HNOI2009]梦幻布丁(链表+启发式合并)
  • 原文地址:https://www.cnblogs.com/zuiyirenjian/p/3055650.html
Copyright © 2011-2022 走看看