正文

MFC学习之--MFC窗口过程2007-08-05 00:23:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/qiutao/28195.html

分享到:

所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。

那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发?

 

MFC窗口过程的指定

从前面的讨论可知,每一个窗口类都有自己的窗口过程。正常情况下使用该窗口类创建的窗口都使用它的窗口过程。

MFC的窗口对象在创建HWND窗口时,也使用了已经注册的窗口类,这些窗口类或者使用应用程序提供的窗口过程,或者使用Windows提供的窗口过程(例如Windows控制窗口、对话框等)。那么,为什么说MFC创建的所有HWND窗口使用同一个窗口过程呢?

MFC中,的确所有的窗口都使用同一个窗口过程:AfxWndProcAfxWndProcBase(如果定义了_AFXDLL)。它们的原型如下:

LRESULT CALLBACK

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

LRESULT CALLBACK

AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

这两个函数的原型都如符合windows的窗口过程函数原型。

如果动态链接到MFC DLL(定义了_AFXDLL),则AfxWndProcBase被用作窗口过程,否则AfxWndProc被用作窗口过程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE设置正确的模块状态,然后调用AfxWndProc

下面,假设不使用MFC DLL,讨论MFC如何使用AfxWndProc取代各个窗口的原窗口过程。

窗口过程的取代发生在窗口创建的过程时,使用了子类化(Subclass)的方法。所以,从窗口的创建过程来考察取代过程。从前面可以知道,窗口创建最终是通过调用CWnd::CreateEx函数完成的,分析该函数的流程:

CREATESTRUCT结构类型的变量cs包含了传递给窗口过程的初始化参数。CREATESTRUCT结构描述了创建窗口所需要的信息,定义如下:

typedef struct tagCREATESTRUCT {

LPVOID lpCreateParams; //用来创建窗口的数据

HANDLE hInstance; //创建窗口的实例

HMENU hMenu; //窗口菜单

HWND hwndParent; //父窗口

int cy; //高度

int cx; //宽度

int y; //原点Y坐标

int x;//原点X坐标

LONG style; //窗口风格

LPCSTR lpszName; //窗口名

LPCSTR lpszClass; //窗口类

DWORD dwExStyle; //窗口扩展风格

} CREATESTRUCT;

cs表示的创建参数可以在创建窗口之前被程序员修改,程序员可以覆盖当前窗口类的虚拟成员函数PreCreateWindow,通过该函数来修改csstyle域,改变窗口风格。这里cs的主要作用是保存创建窗口的各种信息,::CreateWindowEx函数使用cs的各个域作为参数来创建窗口。

在创建窗口之前,创建了一个WH_CBT类型的钩子(Hook)。这样,创建窗口时所有的消息都会被钩子过程函数_AfxCbtFilterHook截获。

AfxCbtFilterHook函数首先检查是不是希望处理的Hook──HCBT_CREATEWND。如果是,则先把MFC窗口对象(该对象必须已经创建了)和刚刚创建的Windows窗口对象捆绑在一起,建立它们之间的映射(见后面模块-线程状态);然后,调用::SetWindowLong设置窗口过程为AfxWndProc,并保存原窗口过程在窗口类成员变量m_pfnSuper中,这样形成一个窗口过程链。需要的时候,原窗口过程地址可以通过窗口类成员函数GetSuperWndProcAddr得到。

这样,AfxWndProc就成为CWnd或其派生类的窗口过程。不论队列消息,还是非队列消息,都送到AfxWndProc窗口过程来处理(如果使用MFC DLL,则AfxWndProcBase被调用,然后是AfxWndProc)。经过消息分发之后没有被处理的消息,将送给原窗口过程处理。

最后,有一点可能需要解释:为什么不直接指定窗口过程为AfxWndProc,而要这么大费周折呢?这是因为原窗口过程(窗口类指定的窗口过程)常常是必要的,是不可缺少的。

接下来,讨论AfxWndProc窗口过程如何使用消息映射数据实现消息映射。Windows消息和命令消息的处理不一样,前者没有消息分发的过程,以下仅讨论Windows消息。

 

Windows消息的接收和处理

Windows消息送给AfxWndProc窗口过程之后,AfxWndProc得到HWND窗口对应的MFC窗口对象,然后,搜索该MFC窗口对象和其基类的消息映射数组,判定它们是否处理当前消息,如果是则调用对应的消息处理函数,否则,进行缺省处理。

下面,以一个应用程序的视窗口创建时,对WM_CREATE消息的处理为例,详细地讨论Windows消息的分发过程。

假如类CTview要处理WM_CREATE消息,使用ClassWizard加入消息处理函数CTview::OnCreate。下面,看这个函数怎么被调用:

视窗口最终调用::CreateEx函数来创建。由Windows系统发送WM_CREATE消息给视的窗口过程AfxWndProc,参数1是创建的视窗口的句柄,参数2是消息IDWM_CREATE),参数34是消息参数。根据处理过程得出判断。例如,“CWnd::WindowProc”表示CWnd类的虚拟函数WindowProc被调用,并不一定当前对象是CWnd类的实例,事实上,它是CWnd派生类CTview类的实例;而“CTview::OnCreate”表示CTview的消息处理函数OnCreate被调用。下面描述每一步的详细处理。

从窗口过程到消息映射

首先,分析AfxWndProc窗口过程函数。

AfxWndProc的原型如下:

LRESULT AfxWndProc(HWND hWnd,

UINT nMsg, WPARAM wParam, LPARAM lParam)

如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(该消息被MFC内部用来确认窗口过程是否使用AfxWndProc),则从hWnd得到对应的MFC Windows对象(该对象必须已存在,是永久性<Permanent>对象)指针pWndpWnd所指的MFC窗口对象将负责完成消息的处理。这里,pWnd所指示的对象是MFC视窗口对象,即CTview对象。

然后,把pWndAfxWndProc接受的四个参数传递给函数AfxCallWndProc执行。

AfxCallWndProc原型如下:

LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd,

UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)

MFC使用AfxCallWndProc函数把消息送给CWnd类或其派生类的对象。该函数主要是把消息和消息参数(nMsgwParamlParam)传递给MFC窗口对象的成员函数WindowProcpWnd->WindowProc)作进一步处理。如果是WM_INITDIALOG消息,则在调用WindowProc前后要作一些处理。

WindowProc的函数原型如下:

LRESULT CWnd::WindowProc(UINT message,

WPARAM wParam, LPARAM lParam)

这是一个虚拟函数,程序员可以在CWnd的派生类中覆盖它,改变MFC分发消息的方式。例如,MFCCControlBar就覆盖了WindowProc,对某些消息作了自己的特别处理,其他消息处理由基类的WindowProc函数完成。

但是在当前例子中,当前对象的类CTview没有覆盖该函数,所以CWndWindowProc被调用。

这个函数把下一步的工作交给OnWndMsg函数来处理。如果OnWndMsg没有处理,则交给DefWindowProc来处理。

OnWndMsgDefWindowProc都是CWnd类的虚拟函数。

OnWndMsg的原型如下:

BOOL CWnd::OnWndMsg( UINT message,

WPARAM wParam, LPARAM lParam,RESULT*pResult );

该函数是虚拟函数。

WindowProc一样,由于当前对象的类CTview没有覆盖该函数,所以CWndOnWndMsg被调用。

CWnd中,MFC使用OnWndMsg来分别处理各类消息:

如果是WM_COMMAND消息,交给OnCommand处理;然后返回。

如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。

如果是WM_ACTIVATE消息,先交给_AfxHandleActivate处理(后面5.3.3.7节会解释它的处理),再继续下面的处理。

如果是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理;然后返回。

如果是其他的Windows消息(包括WM_ACTIVATE),则

首先在消息缓冲池进行消息匹配,

若匹配成功,则调用相应的消息处理函数;

若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否处理当前消息。这里,消息目标即CTview对象。

如果消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;

否则,该消息没有被应用程序处理,OnWndMsg返回FALSE

 

附上消息处理的主要源码:

//在此函数中进行窗口过程的取代

LRESULT CALLBACK

_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)

{

       _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();

       if (code != HCBT_CREATEWND)

       {

              // wait for HCBT_CREATEWND just pass others on...

              return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,

                     wParam, lParam);

       }

 

       ASSERT(lParam != NULL);

       LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;

       ASSERT(lpcs != NULL);

 

       CWnd* pWndInit = pThreadState->m_pWndInit;

       BOOL bContextIsDLL = afxContextIsDLL;

       if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL))

       {

              // Note: special check to avoid subclassing the IME window

              if (_afxDBCS)

              {

                     // check for cheap CS_IME style first...

                     if (GetClassLong((HWND)wParam, GCL_STYLE) & CS_IME)

                            goto lCallNextHook;

 

                     // get class name of the window that is being created

                     LPCTSTR pszClassName;

                     TCHAR szClassName[_countof("ime")+1];

                     if (DWORD_PTR(lpcs->lpszClass) > 0xffff)

                     {

                            pszClassName = lpcs->lpszClass;

                     }

                     else

                     {

                            szClassName[0] = '\0';

                            GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName));

                            pszClassName = szClassName;

                     }

 

                     // a little more expensive to test this way, but necessary...

                     if (::AfxInvariantStrICmp(pszClassName, _T("ime")) == 0)

                            goto lCallNextHook;

              }

 

              ASSERT(wParam != NULL); // should be non-NULL HWND

              HWND hWnd = (HWND)wParam;

              WNDPROC oldWndProc;

              if (pWndInit != NULL)

              {

                     AFX_MANAGE_STATE(pWndInit->m_pModuleState);

 

                     // the window should not be in the permanent map at this time

                     ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);

 

                     // connect the HWND to pWndInit...

                     pWndInit->Attach(hWnd);

                     // allow other subclassing to occur first

                     pWndInit->PreSubclassWindow();

 

                     WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();

                     ASSERT(pOldWndProc != NULL);

 

                     // subclass the window with standard AfxWndProc

                     WNDPROC afxWndProc = AfxGetAfxWndProc();

                     //以下这句即发生取代

                     oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,

                            (DWORD_PTR)afxWndProc);

                     ASSERT(oldWndProc != NULL);

                     if (oldWndProc != afxWndProc)

                            *pOldWndProc = oldWndProc;

 

                     pThreadState->m_pWndInit = NULL;

              }

              else

              {

                     ASSERT(!bContextIsDLL);   // should never get here

 

                     static ATOM s_atomMenu = 0;

                     bool bSubclass = true;                 

 

                     if (s_atomMenu == 0)

                     {

                            WNDCLASSEX wc;

                            memset(&wc, 0, sizeof(WNDCLASSEX));

                            wc.cbSize = sizeof(WNDCLASSEX);

                            s_atomMenu = (ATOM)::AfxCtxGetClassInfoEx(NULL, _T("#32768"), &wc);

                     }

 

                     // Do not subclass menus.

                     if (s_atomMenu != 0)

                     {

                            ATOM atomWnd = (ATOM)::GetClassLongPtr(hWnd, GCW_ATOM);

                            if (atomWnd == s_atomMenu)

                                          bSubclass = false;

                     }

                     else

                     {                  

                            TCHAR szClassName[256];

                            if (::GetClassName(hWnd, szClassName, 256))

                            {

                                   szClassName[255] = NULL;

                                   if (_tcscmp(szClassName, _T("#32768")) == 0)

                                          bSubclass = false;

                            }

                     }                  

                     if (bSubclass)

                     {

                            // subclass the window with the proc which does gray backgrounds

                            oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);

                            if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL)

                            {

                                   SetProp(hWnd, _afxOldWndProc, oldWndProc);

                                   if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc)

                                   {

                                          GlobalAddAtom(_afxOldWndProc);

                                          SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)_AfxActivationWndProc);

                                          ASSERT(oldWndProc != NULL);

                                   }

                            }

                     }

              }

       }

 

lCallNextHook:

       LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,

              wParam, lParam);

 

#ifndef _AFXDLL

       if (bContextIsDLL)

       {

              ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);

              pThreadState->m_hHookOldCbtFilter = NULL;

       }

#endif

       return lResult;

}

 

/////////////////////////////////////////////////////////////////////////////

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

 

LRESULT CALLBACK

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

{

       // special message which identifies the window as using AfxWndProc

       if (nMsg == WM_QUERYAFXWNDPROC)

              return 1;

 

       // all other messages route through message map

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

       ASSERT(pWnd != NULL);                              

       ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);

       if (pWnd == NULL || pWnd->m_hWnd != hWnd)

              return ::DefWindowProc(hWnd, nMsg, wParam, lParam);

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

}

 

/////////////////////////////////////////////////////////////////////////////

// Official way to send message to a CWnd

 

LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,

       WPARAM wParam = 0, LPARAM lParam = 0)

{

       _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();

       MSG oldState = pThreadState->m_lastSentMsg;   // save for nesting

       pThreadState->m_lastSentMsg.hwnd = hWnd;

       pThreadState->m_lastSentMsg.message = nMsg;

       pThreadState->m_lastSentMsg.wParam = wParam;

       pThreadState->m_lastSentMsg.lParam = lParam;

 

#ifdef _DEBUG

       _AfxTraceMsg(_T("WndProc"), &pThreadState->m_lastSentMsg);

#endif

 

       // Catch exceptions thrown outside the scope of a callback

       // in debug builds and warn the user.

       LRESULT lResult;

       TRY

       {

#ifndef _AFX_NO_OCC_SUPPORT

              // special case for WM_DESTROY

              if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))

                     pWnd->m_pCtrlCont->OnUIActivate(NULL);                        

#endif

 

              // special case for WM_INITDIALOG

              CRect rectOld;

              DWORD dwStyle = 0;

              if (nMsg == WM_INITDIALOG)

                     _AfxPreInitDialog(pWnd, &rectOld, &dwStyle);

 

              // delegate to object's WindowProc

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

 

              // more special case for WM_INITDIALOG

              if (nMsg == WM_INITDIALOG)

                     _AfxPostInitDialog(pWnd, rectOld, dwStyle);

       }

       CATCH_ALL(e)

       {

              lResult = AfxProcessWndProcException(e, &pThreadState->m_lastSentMsg);

              TRACE(traceAppMsg, 0, "Warning: Uncaught exception in WindowProc (returning %ld).\n",

                     lResult);

              DELETE_EXCEPTION(e);

       }

       END_CATCH_ALL

 

       pThreadState->m_lastSentMsg = oldState;

       return lResult;

}

/////////////////////////////////////////////////////////////////////////////

// main WindowProc implementation

 

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;

}

阅读(12197) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册