进入MFC讲坛的前言(三)
MFC中的窗口创建及窗口消息映射
我经常碰到有人问我有关窗口创建的问题,他们经常把用HWND描述的系统窗口对象和用CWnd描述的MFC的窗口对象混淆不清。这两者之间是紧密联系在一起的,但是MFC为了自身的管理,在CWnd中加了一些额外的内容,包括如何从HWND生成CWnd。
在MFC中,有几种典型的窗口对象,CWnd描述的一般窗口对象,CView描述的视图对象,CFrameWnd描述的SDI框窗对象,CMDIFrameWnd描述的MDI框窗对象等等。在这一章中,主要讨论下述内容:
MFC中窗口的创建
MFC的消息映射机制(MESSAGE MAP)
对于上面两点MFC的设计者们使用了很高的技巧来确保应用程序的代码尽可能小,其中的技巧和隐藏在它们背后的思想值得我们学习。下面对各项内容进行讨论。
MFC中窗口的创建
在Window下,创建窗口可以使用两个函数,CreateWindow()和CreateWindowEx(),它们都需要一个参数,这个参数是标识窗口类的字符串。所以,如果要创建窗口,一般的做法是,先使用RegisterClass()或RegisterClassEx()注册一个窗口类,然后使用该窗口类来创建窗口。在前面我也提到过,注册窗口类的最主要目的是为系统提供窗口函数的地址,以便被DispatchMessage()之类的函数回。
在MFC中,创建窗口的函数是CWnd或其派生类的Create()或CreateEx方法,注册窗口类一般使用AfxRegisterWndClass(),在这个全局函数中,并没有发现窗口函数地址这样的参数,因此脑子里自然就会有这样的问题:窗口函数在哪里?它是如何同窗口关联的?下面我们将对MFC的一些与此有关的代码进行仔细分析,回答上述两个问题。
窗口函数
在MFC中,有一个全局的函数AfxWndProc(),正如下面的注释所示,它就是CWnd及所有从它派生的窗口类的窗口函数,它的实现如下:
// 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->m_hWnd == hWnd);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
AfxCallWndProc()调用pWnd对象的虚拟函数WindowProc(),它的代码如下:
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()是用来处理该窗口消息的函数,如果某条消息没有被OnWndMsg()处理,也就是该窗口没有提供处理该消息的函数,它就调用DefWindowProc()进行处理,DefWindowProc()也是一个虚拟函数,看看它的代码:
LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
if (m_pfnSuper != NULL)
return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
WNDPROC pfnWndProc;
if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)
return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);
else
return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);
}
DefWindowProc()的策略很简单,调用基类的窗口函数m_pfnSuper来处理该消息。
通过上面的分析,可以得出这样的结论:与其说AfxWndProc()是MFC的唯一窗口函数,还不如说AfxWndProc()是MFC的窗口消息分发中心。正是由于有了这个消息分发中心,才使得MFC的应用程序能够用有限的几个窗口类,作出各种形形色色的窗口,使得在应用程序中,增加CWnd的派生类,并不增加系统中窗口类的个数,将对系统资源的使用控制在一个稳定的范围之内。
注册窗口类除了提供窗口函数外,还指定该窗口的一些外观,如是否有标题条,窗口缺省背景等等。在MFC框架中,有框窗、视图和控制条(CControlBar)等,它们除了操作行为不同外,外观等也不相同,所以MFC注册了几种缺省的窗口类。在MFC中,有一个全局函数AfxEndDeferRegisterClass(LONG fToRegister),它用来注册MFC预定义的窗口类,包括同框窗、视图所对应的窗口类。由于它的代码占的篇幅很长,而且实现也很简单,所以就不列出它的代码了,如果你有兴趣,可以在wincore.cpp中找到它的实现代码。
挂接窗口函数 如果你考察过AfxEndDeferRegisterClass()的实现代码,你会对一行代码感到迷惑,下面列出的是AfxEndDeferRegisterClass()的部分代码,带阴影部分的是那一行令人迷惑的代码: BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister) { 。。。。。。 wndcls.lpfnWndProc = DefWindowProc; 。。。。。。 } MFC将所有预定义的窗口类的窗口函数都设置成DefWindowProc。大家都知道,DefWindowProc是Window下为一般窗口提供消息缺省处理的API,它肯定不是应用程序所需要的窗口函数,所以,MFC肯定在某个地方置换了它,置换DefWindowProc的代码在哪里呢? 前面说过,在MFC中创建窗口时是用CWnd的两个虚拟函数Create()和CreateEx(),Create()是通过调用CreateEx()实现的,所以最终窗口的创建都要归结到CreateEx()函数上。因此,我们可以推断MFC在CreateEx()中置换了DefWindowProc。为了证实这一点,看看CWnd::CreateEx()的代码: 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); 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; } 从上面的代码看不出任何显式的置换DefWindowProc的代码,其实,它隐藏在AfxHookWindowCreate(this)之中,顺藤摸瓜,再看看AfxHookWindowCreate()的代码: void AFXAPI AfxHookWindowCreate(CWnd* pWnd) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); if (pThreadState->m_pWndInit == pWnd) return; if (pThreadState->m_hHookOldCbtFilter == NULL) { pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId()); if (pThreadState->m_hHookOldCbtFilter == NULL) AfxThrowMemoryException(); } ASSERT(pThreadState->m_hHookOldCbtFilter != NULL); ASSERT(pWnd != NULL); ASSERT(pWnd->m_hWnd == NULL); // only do once ASSERT(pThreadState->m_pWndInit == NULL); // hook not already in progress pThreadState->m_pWndInit = pWnd; } AfxHookWindowCreate()设置了一个线程级的CBT Hook,该Hook的入口地址为_AfxCbtFilterHook,_AfxCbtFilterHook是一个全局的MFC函数,_AfxCbtFilterHook通过调用SetWindowLong()用AfxWndProc()的入口地址置换掉DefWindowProc。 小结: 1、在MFC中,创建一个窗口的过程是: 1、生成一个对应窗口类的对象 2、调用该对象的Create()或CreateEx()方法 3、在CreateEx()中(Create()是调用CreateEx()实现的),先调用虚拟函数PreCreateWindow(),让应用程序有一个改变窗口行为的机会,同时,在PreCreateWindow中,还作了一件很重要的事情,就是如果指向窗口类的字符串指针为NULL,就调用AfxDeferRegisterClass()注册一个合适的窗口类,将该注册的窗口类作为创建窗口的类型参数。AfxDeferRegisterClass()就是AfxEndDeferRegisterClass(),后者根据参数注册相应的窗口类,并将DefWindowProc作为该窗口类的临时窗口函数。 4、CreateEx()在调用CreateWindowEx()创建真正的窗口对象之前,设置一个线程级的CBT Hook,该hook在窗口创建完成后被调用,MFC在hook函数中调用SetWindowLong()将该窗口的窗口函数置换成AfxWndProc。 2、从Window的角度看,任何一个MFC应用程序都只有一个窗口函数AfxWndProc。 3、AfxWndProc的作用是截获所有的发送给窗口消息,并将这些消息发送给相应的窗口对象的窗口函数WindowProc处理,所以它实质上是一个窗口消息分发器,注意,非窗口消息不被AfxWndProc所分发,它们在AfxWndProc被调用之前就被CWinThread::PreTranslateMessage()处理过了。 4、同PreTranslateMessage()不同的地方在于,AfxWndProc()能够截获所有的来自于消息队列的和非消息队列的窗口消息(如调用SendMessage()发送的窗口消息)。 |
进入MFC讲坛的前言(三)的更多相关文章
- 进入MFC讲坛的前言(四)
MFC的消息映射机制 MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快.为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息 ...
- 进入MFC讲坛的前言(二)
MFC的WinMain 使用MFC编程的程序员刚开始都会提出这样一个问题:我的程序是从哪儿开始执行的?回答是:从WinMain()开始执行的.提出这样的问题是由于在他们所编写的MFC应用中看不到Win ...
- 进入MFC讲坛的前言(一)
在这里,我想谈谈自己学习MFC的一些体会.我是从1997年才开始在Window下编写程序的.在这之前,我编写过一些DOS程序,包括一个简单的全屏幕编辑器和一个带函数的表达式解释器,都是一些小的程序.W ...
- 进入MFC讲坛的前言(五)
框窗.视图和文档及其关系 MFC架构的另外一个特色是它的框窗.视图和文档这个三位一体的结构,它是一个典型的MVC(Model.View and Controler)结构.严格的讲,框窗不属于MVC中的 ...
- MFC添加背景图片三种方法
方法一: 1.声明成员变量CBrush m_brush;2.在InitDialog中添加代码: ? CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP1); //IDB_B ...
- 深入浅出MFC学习笔记 第三章 MFC六大关键技术之仿真
0:MFC类层次结构 1:MFC程序的初始化过程CWinApp::InitApplication()CMyWinApp::InitInstance()CMyFrameWnd::CMyFrameWnd( ...
- OpenGL于MFC使用汇总(三)——离屏渲染
有时直接创建OpenGL形式不适合,或者干脆不同意然后创建一个表单,正如我现在这个项目,创建窗体不显示,它仅限于主框架.而我只是ActiveX里做一些相关工作,那仅仅能用到OpenGL的离屏渲染技术了 ...
- MFC 小知识总结三
1 载入资源位图 1 获取程序实例 HINSTANCE m_hInst; m_hInst=AfxGetInstanceHandle(); //获取程 ...
- MFC学习笔记2---简单计算器
前言 学习了鸡啄米网页的前三部分后,我们就可以做一个小软件出来了,我选择先做一个计算器. 这是Win7系统自带的计算器: 为了提升成就感,我将计算器的大部分内容去除,于是就变成这样: 这样就只剩下了1 ...
随机推荐
- 模拟美萍加密狗--Rockey2虚拟狗(五)
虚拟狗开源后很多网友询问有关使用方法的问题,其实看我前四篇文章就应该了解怎样使用了,但还是写篇教程吧 [一].安装DSF (驱动模拟环境): 运行DSFx86Runtime.msi 如需改变安装目录请 ...
- hdu4370 0 or 1【最短路+建图】
转载请注明出处,谢谢:http://www.cnblogs.com/KirisameMarisa/p/4297627.html ---by 墨染之樱花 题目链接:http://acm.hdu.ed ...
- 在C#中使用 Win32 和其他库
C# 用户经常提出两个问题:“我为什么要另外编写代码来使用内置于 Windows® 中的功能?在框架中为什么没有相应的内容可以为我完成这一任务?”当框架小组构建他们的 .NET 部分时,他们评估了为使 ...
- 比较和排序 IComparable And IComparer
1.List<Student>默认排序 为类创建默认排序实现IComparable,此代码的实现为年龄升序 using System; using System.Collections.G ...
- 从开发到部署,使用django创建一个简单可用的个人博客
本文参考于: 简书-Django搭建简易博客教程:http://www.jianshu.com/p/d15188a74104 自强学堂-Django基础教程:http://www.ziqiangxue ...
- Spring源码地址和相关介绍的网址
Spring源码地址下载: https://github.com/spring-projects/spring-framework/tags >多图详解Spring框架的设计理念与设计模式:ht ...
- 在Visual Studio中使用FFTW库
FFTW不用做过多介绍,一般都是在linux下使用,包括放出windows版的作者也自己声明:we don't use Windows ourselves 但是由于要和MIC混合编程,想先在本地使用, ...
- BZOJ 1567: [JSOI2008]Blue Mary的战役地图( 二分答案 + hash )
二分答案, 然后用哈希去判断... ------------------------------------------------------------------------- #include ...
- js常用几种类方法实现
js定义类方法的常用几种定义 1 定义方法,方法中包含实现 function createCORSRequest() { var xhr = new XMLHttpRequest(); xhr.onl ...
- Java Web学习笔记(1)
1.项目名称用小写,类名用大小写骆驼式,对象名用骆驼式但是第一个字母是小写: 2.写对象属性时要空行,第一个方法也要空行,一般要加注释: 3.new 新的对象时等号左右要空格,if语句左右摇有空格: ...