Windows MFC 工程应用开发与框架原理完全剖析教程(下

    • 消息循环基础类CCmdTarget的设计
    • 消息循环基础类CWnd的定义
    • 消息循环基础类CWnd的句柄映射
    • 消息循环基础类CWnd的窗口注册与消息分发
    • 消息循环基础类CWnd中MFC使用消息钩子技术拦截所有到本应用程序的消息并重设窗口回调函数
    • 消息循环基础类CWnd中CreateEx的实现
    • 消息循环基础类CWnd的实现与单元测试验证
    • 消息映射的剖析与实现(1)——AfxSig、DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP与END_MESSAGE_MAP究竟是在做什么事情
    • CWnd、CCmdTarget默认消息响应实现
    • 消息映射的剖析与实现(3)——单元测试构建以及MFC核心功能验证
  • 第四章 工程篇-MFC通用技术
    • 导论:文档视图模型-从志玲姐姐的照片说起
    • 纯手工打造一个文档视图模型应用程序
    • 图解文档视图模型、手工生成菜单消息映射与视图消息映射

消息循环基础类CCmdTarget的设计

MFC在你的框架起来之前,就已经挂钩了Windows系统对你这个应用程序的推进,从而能够彻底的掌握窗口的生与死。

把我们不需要的测试文件里面的内容注释掉:

我们编译运行下TestCWinApp.cpp是否生成成功,在此基础之上我们开始进一步的编码。

接下来我们来引出CWnd的实现。

这个CCmdTarget是MFC抽象、独立出来的一个类,它认为所有具有消息循环、或者响应消息的,都用这个类来进行封装;
所以我们要对CThread类进行改进,让它具备消息映射的能力,首先在CWinThread之前要定义一个CCmdTarget类。

同时我们希望CWinThread能够持有消息循环当中的窗口,这样的话我们的CWinApp(因为CWinApp是CWinThread的子类)的父类CWinThread当中就有了窗口这样一个句柄,所以大家就会知道了我们的CWinApp可以作为一个HINSTANCE去持有一个窗口。

在考虑到我们的CCmdTarget实现之前,我们还得考虑到窗口有不同的类型,并且希望我们的窗口能够支持各种各样的样式,那么首先我们还得建一个辅助的定义,我们用头文件把它抽象出来。

这些主要是为了方便MFC动态跟踪的。

我们一个MFC框架里面可能会有多个窗体、或者多个View视图结构。

消息循环基础类CWnd的定义

因为你要知道我们这个Windows对象是个Windows的一个数据结构,而在我们C++当中的时候,它可能会有临时对象,也有可能有永久对象,所以我们临时的有一个对象拿出来的话,我们就用FromHandle给你一个临时的对象,如果你想要的确确实实附加到一个实实在在的CWnd当中去,我们就FromHandlePremanent,这就是大家知道的attach和detach的区别这里面就开始有了。

我如果想把一个Windows对象HWND变成一个C++对象CWnd的话,我就得把HWND给attach上去;
同样的,我现在这个C++对象CWnd如果在C++运行时要把它析构的时候(窗口校会的时候),我还要把这个资源还给Windows,这个时候我就Detach;
而在我这个程序运行的过程当中,我可能会创建出各种各样的窗体,然后每一个窗体出来的时候都会有一个HWND,那我就得看看我现在是需要把它临时做一个CWnd给我用呢,那我就用FromHandle拿到,如果不是临时用,而是在一个比较长的完整的生命周期获得它呢,那就FromHandlePermanent,这就是这两个的区别。

你既然有CreateEx了,为什么还要有PreCreateWindow呢?
你要知道在我们这个窗体创建之前,实际上它(窗口函数)已经在接收消息循环了,所以我们得把一些内容预先封装到这个函数里面来做。
PostNcDestroy这个函数是因为这个窗体去掉了以后,其实它里面还有很多资源需要我们逐一回收的。
PreSubClassWindow,这个叫做窗口子类化,什么叫窗口子类化呢?
你还记得我们曾经在AppWizard当中,我们可以通过AppWizard把一个Windows的资源变成一个C++对象,那这个时候实际上窗口里面的子窗口就变成了一个类,那这个时候在我们MFC中必须考虑这样的问题,否则MFC是没有办法管理这个里面的资源的,所以这里又定义了这么一个函数。

在现在这个环境当中我们先不实现消息循环,但是我们得把这样一个内容先留在这里。

在我们工程当中的友元函数,并不是简简单单的所谓说让一个类访问另一个类的成员,它主要是为了完成C/C++的混合编程。
AfxCallWndProc这必然是全局函数,否则的话你这个挂钩会比较难以实现。

接下来我们还要定义一些CWnd的全局函数,以方便这种消息的传递:

消息循环基础类CWnd的句柄映射

我们要到AFX_MODULE_THREAD_STATE类做个补充,我们要把我们的CHandleMap给放进来:

有了这个成员,现在我们的CWinThread才有了管理窗口的能力。

进一步的为了规范性我们还得再加一个头文件,来帮助我们规范管理:

我们需要有一个CHandleMap这个类帮我们管理从窗口的句柄HANDLE映射到我们CWnd这样的指针。

我们新加一个头文件,这个是咱们窗口的核心,叫WINCORE.cpp,完成窗口句柄的映射。

我要把你的线程和你的这样一个窗体映射起来。

通过咱们这个afxMapHWND函数,就通过咱们线程推进,把咱们这样一个C++对象和一个HWND对象关联起来了。

这个时候大家一定要注意,咱们这个线程和它的关联关系,换句话说我们是通过一个线程把我们的这样一个内容给它关联起来。

如果pMap不为空,那我们就看pWnd的下一个是不是存在CWnd对象,总归我要换一个给你。

把当前的C++对象和HWND进行匹配。

看看它存在不存在,如果不存在就创建呗。

为了让大家理解,我们再来看看这个MFC封装的这样一个线程结构,我们这边的HWND已经开始通过pState开始一个一个放到上图右边槽2里面来了,接下来也就是说我们MFC在推进你这段代码的时候,它都是通过咱们这个线程来推进的,所以呢,当一个Windows对象被我放进来的时候,就是我们讲的HWND对象放进来的时候,我一定要挂到我这个C++对象里面去,所以在我们这个代码当中就有了这样的内容:

我们在一上来的时候,先从AFX_MODULE_THREAD_STATE开始拿,拿你的模块,然后在模块里面开始完成映射,而这个里面的这个映射有一个地方在这里要说的:

从上图右边槽2那里开始起它都是一个句柄对句柄,我不可能把真正的数据结构放在里面,我告诉你这个窗口的句柄是什么,我还知道我的C++句柄是什么,完成这个对应关系的映射。
这个大家一定要建立这个动态观,否则你对MFC为什么要封装线程会有这个疑惑的,因为我们这个程序一旦运行起来的时候,它就是通过线程来推进的,所以它只有在这一个线程的空间里面完成这个数据结构和数据结构的对应,才能够去管理你这个对象的生和死;
C++运行时只能负责C++对象,也就是说C++运行时只能负责CWnd,但是C++运行时它是管不到我们这个Windows对象的,所以我要让它建立起关联,这个关联必须由我们自己来维护,所以我们这边得有一个句柄的映射,这个地方请大家仔细体会。

消息循环基础类CWnd的窗口注册与消息分发

这是咱们的窗口回调过程。
CWnd::FromeHandlePermanent(hWnd);这个函数是借助AFX_MODULE_THREAD_STATE里面的方法获得的所传入的窗口句柄对应的CWnd对象指针。

这个时候我们要借助我们线程管理开始拿它里面的状态,同时为了防止事件冒泡,我们保存旧的消息,在该函数返回时恢复;
然后更新本线程中的变量m_lastSendMsg的值(更新线程私有数据)。

这个就是我们讲的默认的DefWindowProc,也就是Window消息分发,这里暂时先返回NULL。


然后看一下我们真正的注册,我们要按照线程的思想进行推进:

所以你有时候用spy++能看到一些奇怪的类名(包含数字、冒号之类的),就是这么来的(上图hIcon应改为hInst)。

我们让它的窗口回调过程指向默认的DefWindowProc,讲白了我们还是把它放在消息循环里面去,但是这个放进去了以后,这个的接管不是直接路由给你一个wc这样一个结构,而是将wc这个结构路由给了我的MFC,这是非常重要的问题。

(上图的指针符号*应改为按位与符号&)

消息循环基础类CWnd中MFC使用消息钩子技术拦截所有到本应用程序的消息并重设窗口回调函数

在窗口创建之前实际上这个应用程序已经在接收消息了,因为你的窗口消息回调函数是WNDPROC,而你的窗口在注册的时候走的是WinMain,我们说过WinMain和WNDPROC这两个函数是被操作系统割裂开来的。

为了让我的MFC感知到你这个窗口的资源(窗口上的子窗口之类的资源),允许子类化窗口的事件先完成。

接下来,我们要改变窗口的默认回调函数,我们说过了一开始是指向默认的,现在我不能让它默认了,我得让它指向我自己的窗体了,我来接管你的消息映射了,这个为我们接下来做消息循环,做成员函数的消息路由做准备。

如果相等的话直接就return了,说明你已经创建成功钩子了。

上述代码中的关键是要把相关消息过滤给谁,给_AfxCbtFilterHook这个钩子函数;
这个时候就把我的线程关联上了,告诉你是我当前这个线程要截获你这个消息,并且是由当前的线程在主导你整个的窗体的回调。

你的创建完了以后我还得把消息钩子给去掉,因为钩子是占系统资源的:

换句话说,我抓这个钩子的目的是做什么呢,我要拦截下消息,让这个消息变成我自己的窗口函数,当我的窗口函数执行完毕了以后,我要把这个钩子给去掉,否则的话会增加系统消耗的。

就是说这个时候你钩子没装,你钩子没装的话MFC没有办法控制你这个线程的状态,控制不了线程的状态就控制不了你程序的推进,控制不来你程序的推进那就不要做了,直接return一个FALSE。

消息循环基础类CWnd中CreateEx的实现

这个时候大家就应该知道了,为什么在我们创建窗口的时候会用矩形RECT这个参数,原因是MFC只给了这个东西。

你这边真正感知的是哪个呢,那又要借助你的线程局部存储管理类了,从里面拿你当前的应用程序实例。

通过AfxUnhookWindowCreate函数来判断MFC有没有帮你把这个消息映射建立在自己的窗口过程当中呢?
如果没有的话,那就说明CreateEx失败,也是调PostNcDestroy;

接下来再判断一下,可能资源不够的话,还得判断下hWnd等不等于空,等于空的话说明资源不够了,窗体没有帮你分配,return FALSE;
如果hWnd不为空,那就过五关斩六将了,return TRUE,我创建完毕了,可以等待消息响应了。

接下来我要拿你现在的消息,因为这是为了我们下一步消息路由做准备的:

我要拿你的线程状态用来推进。

我们回到_AFXWIN.h头文件中,把上面实现的函数的定义补充进去:

我们编译下,发现有若干个语法错误,我们开始进行修订。

消息循环基础类CWnd的实现与单元测试验证

我们首先解决重定义的问题,给头文件加上#ifndef预编译指令来防止头文件重复包含。

从上图我们可以看到出现了两个内容。

这个是前面VS智能提示以及我们粗心导致的拼写错误,应改为:

在WINCORE.cpp最后添加如上图所示的IMPLEMENT_DYNCREATE宏后,就通过了编译器的语法检查;
接下来我们完成一个单元测试,看看我们这个程序是否已经能够完成这个我们窗口创建的能力了。

从此以后我们就既看不到我们的这个WinMain函数,也看不到我们的HWND了,只有C++的CWnd对象。

最后,塞给我们Windows默认的窗口过程,

有错误,说明我们这边还有一些完全没有实现的,我们接下来再来完善一下。

这个就是说和我们前面的单元测试内容冲突了,我们在TestCWinApp.cpp中ctrl+A全选后ctrl+k ctrl+c把内容注释掉。

重新生成一下,发现还有问题:

上图是说这个函数没有实现,我们到_AFXWIN.h中看一下该函数的声明,发现这两个函数名字不一样,实现那里的函数名字拼写有错误:

我们修改后重新生成一下:

仍然是拼写错误,我们修正一下。

我们的窗体创建出来了,点击关闭按钮也能关掉,证明我们的窗体已经OK了。

消息映射的剖析与实现(1)——AfxSig、DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP与END_MESSAGE_MAP究竟是在做什么事情

这里帮大家做一个简单的回顾:
这个HWND是一个指针,这个CWnd实际上也是一个指针,这样的话我们借助前面我们写的CMapPtrToPtr类完成窗口句柄的映射,接下来我们在MFC当中不停的利用我们那个线程状态对象(AFX_MODULE_THREAD_STATE),来不停的获取当前MFC推进程序所获得的句柄和关联关系,并且把这个窗口句柄和我们的这样一个C++对象(CWnd)进行关联;
在此之前,我们如果要想让MFC完成全部的推进,我们还加了消息钩子,让我们的C++运行时构建出来的CWnd对象出来之前,就具有了管理Windows对象的能力;
接下来我们还有一个比较重要的环节,就是我们下面要讲的消息映射的最终实现,这个做完了以后我们对MFC的核心已经全部模拟仿真成功了,在这个过程当中我们主要和大家介绍DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP与END_MESSAGE_MAP究竟是在做什么事情,同时我们也会和大家介绍我们MFC当中的困惑,AFXMSG这个占位符究竟是做什么事情的。

接下来在我们前面的03CWnd项目的基础上,完成我们这样一个核心MFC。

我们重新生成一下,运行后是我们上一节运行的结果,把窗体创建出来了。

为了防止上次的错误,我们把Example.h和Example.cpp里面的内容都注释掉:

接下来我们就可以开始消息循环的加入,并完善起我们MFC的核心框架。

那我们首先要完善的第一个问题,就是说我们这个东西既然能够被验证,肯定是需要一些函数签名的标识,我们添加一个头文件叫做_AFXMSG_.h,来帮助我们去完成相应的内容。

这个就是帮助MFC框架来区别出你究竟是MFC要做的消息函数呢,还是普通函数。

我们不可能把所有的Windows消息全部实现,我们提供这个基类(或者说是模板),把几个常见的放在这里面,这是一个非常庞大的工程,我们主要是学习这个框架的设计精髓,帮助我们有效的控制框架,并且改进框架。

可以认为数字签名中的v代表void,w代表UINT,i代表int,s代表指针,例如上图中的AfxSig_vw的示例函数举例有void OnTimer(UINT nIDEvent)函数。
有了这些全局变量的声明,在初始化消息映射表时,就能够记录下消息处理函数的类型了。

接下来我们列举若干个自定义的MFC消息,讲白了我们现在就是想把Windows里面的Windows消息变成成员函数。

前面的是Windows消息,然后是函数签名,最后是函数的入口地址。
这个也就是大家后面会知道的,为什么我们用那个Document/View这样的结构当中的时候,为什么这些消息都是被Viewd截获,因为你看它就定义在CWnd这里嘛。

通过这样的宏定义过程,大家应该能够比较熟悉或了解我们现在做的事情,就是把Windows消息变成一个个可以对应的入口地址。

好了,有了我们这个AFXMESSAGEMAP的协助,我们接下来就可以帮助大家回答两个问题,第一个AFX_PMSG是什么,还有一个我们怎么让我们的消息循环给它添加进来。

我们还要对CCmdTarget这个类加内容:

AFX_PMSG这个就是我们的占位,通过这个占位我们才能够知道,凡是符合我们CCmdTarget::AFX_PMSG这样的一个函数,它有什么样的运行过程;
接下来我们就会告诉你,有了这样的定义,我们就可以开始来说典型的映射表项和消息映射表了。

pBaseMap是基类的消息映射表的地址,比如说你当前有一个窗体,这个窗体消息你不感兴趣,但是你的父类感兴趣,那这个时候你还得把父类的消息映射表地址也要进一步路由;pEntries的意思是,如果我感兴趣了,消息映射项的指针放在这里。

上图49行最后为什么要有个反斜杠呢,是因为你接下来的消息处理函数,例如OnPaint,就往里面拼,OnPaint就放在这个反斜杠后面拼在这里。

这样的话CCmdTarget这个类就具有了这种消息控制的能力。

CWnd、CCmdTarget默认消息响应实现

接下来,在CWnd里面也要加这个消息处理的内容,对CWnd进行完善,我们没有需要做这么细,但是常规的要有。

这是把Windows的消息分成3类,你有哪3类,我们默认的就给你哪3类。

afx_msg这个占位就是帮你去遍历里面一个个对应的内容。

你既然做了,最后我们得把你这个消息的内容全部加进去,加入消息循环。


我们现在要让我们这样的消息循环给加入处理,我们还得写一个全局函数,在我们CWinThread之前,这个函数也是我们MFC框架里面的全局函数。

我要通过我的框架,你的这个框架告诉我你要找哪个窗口地址,那我有了这样的信息就给它放进去。

接下来我们就要开始实现CWnd里面的消息循环,但是我们说过了,我们所有有消息映射能力的类都是从CCmdTarget开始往下来的,所以我们要对CCmdTarget也要来做一个消息映射的实现。

接下来我们实现它的真正的消息分发是怎么来做的。

讲白了,我们CWnd中不是有个GetCurrentMessage么:

所以,接下来在OnWndMsg当中message参数不断的捡取消息,OnWndMsg完成的是消息路由。

如果消息是通知消息,它里头会有子控件(因为你的WM_NOTIFY消息都是子控件发出来的),我们逐个调用它的条目,然后去找它里面的内容:

消息映射的剖析与实现(3)——单元测试构建以及MFC核心功能验证

开始构建我们的单元测试,我们希望能通过定时器做个简单的电子时钟把时间显示上来。

这个类名就注册好了,有了这个类名我们就可以去感知到这样的一个WNDCLASS结构,接下来就可以创建窗口了。

接下来我们还要测试下消息映射:

这样子就把我们类的成员函数,和我们的Windows消息关联起来了。

接下来我们开始对这些方法逐个的设计。

我拿到整个客户区的这个窗体大小,存到我的这个变量里面(m_rcInfo),我的这个格子比它的稍微小点;
并把定时器装上去,装到我的当前的HWND这个窗体来,这个500是说每半秒钟做一次更新。

因为每一次我们都要这个时间给它冲掉,所以都要把这个缓冲区给它清零。

要控制它的格式啊,%.2d是为了保证每个时间位都有两个位置,避免从9到10之间差一位,就会显示的时候有问题。

ON_WM_PAINT和ON_WM_TIMER里面多了一个逗号,我们删除掉:

我们按着ctrl+右花括号,可以看到这是花括号匹配问题造成的。

我们选中上图这部分,ctrl+x,把它拿出来放在下面:

我们重新生成一下,看看有没有语法错误。

这是我们的拼写问题。

现在窗体出来了,当时它现在没有出现字体,也没有进入消息循环,我们的窗口没有响应,窗口显示出来了,那么很有可能是窗口过程就没有运行;
我们来看下它的问题出在什么地方:

我们发现窗体显示出来它根本就没有进到OnCreate消息,也就是说WM_PAINT这个消息就没有进来,那接下来我们就来看下窗口的这个过程。

首先我们发现第一个问题,我们要让它进的过程DefWindowProcA不对,我们应该让它进自己的WindowProc:

第二个问题如上图所示,这个地方的lResutl我们没有做处理,它当然出不来了,这是我们一开始为了编译通过暂时做的内容。

我们看看能不能执行到我们自己写的OnWndMsg,调用一下看它能不能进我们的窗口,如果它能进我们窗口,那就说明这个窗口已经进来了,如果再不行我们再看哪个地方还有问题。

这个时候时间显示出来了,PAINT方法也执行了,证明我们这个消息循环和我们整个的过程也建立起来了,那我们的MFC核心功能通过单元测试也和大家演示完毕了。

第四章 工程篇-MFC通用技术

导论:文档视图模型-从志玲姐姐的照片说起

这个视图就相当于志玲姐姐的照片,这个文档就相当于志玲姐姐本人,你想看志玲姐姐,你可以看她的大头照,也可以看它的半身照,也可以看她的全身照,也可以看她的侧面照,这些都是文档也就是志玲姐姐本人映射出去的,换句话说我们经常讲的一档多视,什么叫一档多视呢?
我通过不同的侧面,我看你的侧身照,我看你的半身照,这些都是视图,那由这样的讲法大家就会理解了,原来文档和视图并不是外面有些人讲的数据、或者说数据库这种狭隘的观念,而是这个应用程序运行当中抽象的一个概念,这个抽象的概念就是这一个应用程序它所要处理的业务逻辑本身,而业务逻辑的展现就是视图。

我们要让这样的一个业务逻辑的呈现和业务逻辑本身给它割离开来,MFC的设计者就已经考虑到了这个问题,他把这个文档和视图变成了两个C++对象来供你使用。

那这个时候呢,我如果把这个数据拿出来了以后,换句话说,我已经把志玲姐姐请过来了,我怎么把志玲姐姐展现给大家看,我就拍照片给你,拍出来的照片就是View。
换句话说看,我如何在我的应用程序当中做呢?很简单,我就要去看我怎么去呈现给用户,给用户呈现的比较好,常见的问题,比如说我可以通过一个MFC的方法,我读到当前这一个磁盘当中的目录结构,磁盘当中的目录结构我怎么给用户看呢,我可以切分一个窗口出来,这个窗口切分出来就是一个View,我可以告诉你左边是树状,右边是详细列表,那这些东西都是需要我们进行编程的。
讲白了,我只要有一个View对象,我让这个View对象去持有一个Document的引用,也就是说View和Document就建立起关系来了;
同样的我一个Document对应多个View,有View1,有View2,有View3,View1、View2和View3都持有同一个Document的引用,你想想看,当这个Document发生变化了以后,它通知View1、View2和View3同时发生更新,是不是我就看到了同样的一个数据在不同的展现,就是我把志玲姐姐请过来了,有的人从前面看,有的人从侧面看,有的人从后面看,那看出来的志玲姐姐是不同样的这样一个View,这个就是我们讲的文档-视图的本质含义。

讲白了,我们一句话去总结它:
所谓文档-视图讲的就是当我的一个业务进来了以后,我需要通过不同的窗口展现出一个数据的不同部位。

纯手工打造一个文档视图模型应用程序

我们粘贴两个文件到当前项目目录中,我们主要是为了加内容,首先把Resource.h文件添进来,然后添加资源脚本文件MFCEdu.rc。

我们可以看到Menu全都帮你做好了。

大家可能会说MainFrm这个东西是干嘛的?
在我们这一个MFC当中,看上图的非客户区(就是上面有菜单栏、底下有状态栏的有个边框的就是非客户区),这个非客户区是一个Windows对象,那么MFC把它映射成了一个叫CMainFrm的这么一个C++对象来对应这个非客户区对象。
所以我们仿照这样的MFC来做这样的事情,所以我们这边有个MainFrm.h头文件:

同样的话你不是会讲,我们的app在哪里么?
我们依然添加一个头文件,我们先把架构给你搭出来,这个头文件叫做MFCEdu.h,这个就是将来我们的WinApp方向:

我们仿照MFC的特点,你的MFCEdu里面有个东西,叫做MFCEduDoc,这个就是Document对象,它就在这个地方帮你生成出来;
同样的,照猫画虎,添加一个头文件叫MFCEduView.h这个就是我们将来的View。

在MFC中还有一个东西叫stdafx.h,网上叫它预编译头,这样的话它可以使得我们的编译当中是增量编译,可以加快你的编译速度;

我们这个MFC是一个框架,它一定依赖于afxwin.h,这个头文件放在上述MFCEdu的哪个头文件中都不合适,也就放在stdafx.h当中最好,这个东西就是MFC框架帮你生成出来的;
除了这个东西,还有一些常用的东西会放在里面,afxext.h这是MFC当中的扩展包,没有这个东西的话MFC有些东西可能做不起来;
还有一个常见的afxdtctl.h这个是给internet explorer用的;
还有一个扩展包afxcmn.h这个是通用控件。

那这个时候我们的整个框架已经给你做起来了,当我这个应用程序编译的时候,我已经能够知道MFC的头文件在哪里了,我也知道我的MFC当中的基本特征了,接下来我们开始手工打造MFC这样一个Document和View的这样一个视图结构。

动态创建我的CMainFrame对象。

按照它的要求,我要能够进行修订,我要修改你里面的内容,你不是框架么,你肯定会有一个结构叫做WNDCLASS,MFC把它封装成了叫做CREATGESTRUCT的东西;
PreCreateWindow这个是给派生类来不断的修改里面的内容的。

告诉你我要加入消息循环,你是窗口,你肯定会有消息响应在里面。

View就是你的窗口,它没什么特点,只是这个View是派生于你的CWnd的一个独立的客户区的Windows对象。
GetDocument,这个是用来告诉你,我如何去获得我的MFCEduDoc的这样一个引用;
PreCreateWindow,我可以改变一下我的外观呈现的样式。

同样的,我要让它加入消息循环,它要是用来截获和用户交互的。

为MFCEduView.h添加MFCEduDoc.h头文件:

我们修改MFCEduDoc.h头文件,添加CMFCEduDoc类定义:

这个地方有个叫OnNewDocument函数,这是它特有的,告诉你我的这个Doc它能够新生成一个文档,讲白了就是我这个应用程序出来的时候,我把这个数据放在这个里面来;
Serialize函数,讲白了你既然有数据,我可以存储到非易失性介质,就可以存到硬盘当中去。

首先我们先把非客户区的菜单给它做出来,还有外面的边框做出来。

我把它的消息给它路由,给它关联,让它可以往后传递;然后实现事件冒泡和消息路由,这里如上图所示把结构先放在这里,建立起消息路由的结构。

你既然是个框架,不是说你想创建就创建的,你还得给MFC框架类库做一些动作:

我们完全调用MFC框架给我们做好的支撑(在上图if语句中调用基类提供的函数),我们把外面的框架给你画出来了。

我们接下来要把CWinApp给它构建出来。

这时候它有了入口点,开始构建MFC对象了,我们开始往里面写东西了,最核心最核心的内容就是InitInstance函数,这个东西是构建你,我如何关联你里面的内容呢,它里头有一个叫做模板模式的方法(可以参考设计模式相关内容),对于我们MFC来讲,首先它要干的事情是什么呢,就是告诉你我们建立起这样的关联,这个Template告诉你它做了什么东西呢:
new CSingleDocTemplate
这个里面干的活我告诉你啊,怎么干的呢,怎么去关联的呢?

IDR_MAINFRAME,这是我的一个菜单;
它开始关联你的项目里面建立的每个对象(CMFCEduDoc、CMainFrame、CMFCEduView);
这个模板生成好了以后,我得把这个模板加入到我的MFC框架的管理里面去。

至此,你的这3个类,通过AddDocTemplate方法就把你的Doc、View和你的Frame给它关联起来了。

这个m_pMainWnd就是因为前面的pDocTemplate方法,就已经把CMainFrame给它关联上了。

接着,我们看看我们的Doc是怎么去持久化我这个应用程序里面的数据的。

我们现在派生出来的这个子类CMFCEduDoc就和CDocument产生了关联。

你有消息的话也要路由给我;
这时候大家可能会想了,为什么我的这个数据也要接收消息呢?
很简单,我们在文档当中有了这样的数据以后,拿到这样的一个交互,这个交互我们Doc本身是不产生这样一个交互过程的,但是我们又要响应一些用户里面的操作,我们就通过View对象去转发给Doc对象,让Doc对象处理好了以后,去更新所有的View对象。
可能大家现在听起来比较抽象,反正你就记住这么一句话,我的Doc也是可以参与消息循环的,也能进行用户行为与操作系统的交互。

我出来的时候得有一个新的文档给你建立起来:

再接下来,我的这个内容做完了以后啊,我是数据,我通常是要保存到非易失性介质里面去的:

这里面的意思就是,它如果正在存储的时候怎么办,它恢复的时候又怎么办(也就是加载的时候):

我们再来做View,这个View是不需要Frame的,它关心的是它里面的内容,也就是Doc,所以需要包含的头文件为:

既然是个视图,这个视图多宽啊多高啊,我都可以设置:

当我每次去重绘的时候啊,或者当我去画的时候啊,我首先拿一下这个Doc对象,当我持有这个Doc对象以后,我就可以把Doc对象里面封装的内容取出来,然后我来画,换句话说,我就拿个数据的模板,换句话说我把志玲姐姐请过来了放在这个地方了,比如说我现在这个叫CMFCEduView1,我还可以叫CMFCEduView2,那我画的时候,第一个我只要画一半,第2个我画全身,不就形成了一档多视了么。

我们重新生成一下看看有没有错误;
全部重新生成成功。

图解文档视图模型、手工生成菜单消息映射与视图消息映射

Windows MFC 工程应用开发与框架原理完全剖析教程(下)相关推荐

  1. Windows phone 应用开发[12]-Pex 构建自动化白盒测试[下]

    本篇承接于上篇Windows phone 应用开发[11]-Pex 构建自动化白盒测试[上] .大概了解Pex作为自动化白盒测试工具工作方式.以及提出参数化单元测试的概念.为开发人员减少手动编写大量独 ...

  2. MFC主流开发库BCGControlBar下载安装教程!

    1.BCGControlBar简介 1.1 概述 BCG是MFC的一个扩展库,可以用来构建类似于Microsoft Office 2003/2007/2010/2013/2016 和 Microsof ...

  3. VS2015之博大精深的MFC项目开发(二)

    VS2015之博大精深的MFC项目开发(二) 第二章 MFC原理篇 1.MFC06-1:CString类的测试 1.1 operator+函数 1.2 Delete函数 1.3 Find函数 1.4 ...

  4. Visual C++ MFC/ATL开发-高级篇(一)

    在VC++6.0中用MFC进行COM编程首先应当明确,MFC中是通过嵌套类而不是多重继承来实现COM接口的,通过接口映射机制将接口和实现该接口的嵌套类关联起来:MFC中提供一套简明的宏来实现嵌套类的定 ...

  5. 《精通Windows Sockets网络开发--基于Visual C++实现》.(孙海民).[PDF]ckook

    图书作者: 孙海民 图书编号: 9787115179111 图书格式: PDF 出 版 社: 人民邮电出版社 出版年份: 2008 图书页数: 400-500 [内容简介] windows socke ...

  6. Windows 网络通讯开发

    Windows 网络通讯开发 一.Windows网络开发API 由于C++标准库中没有网络库,所以进行网络开发的时候要调用系统API.Windows通讯开发API包括以下几个基本函数及成员类型: 1. ...

  7. 《MFC游戏开发》笔记五 定时器和简单动画

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9332377 作者:七十一雾央 新浪微博:http:// ...

  8. 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9313239 作者:七十一雾央 新浪微博:http:// ...

  9. 《MFC游戏开发》笔记二 建立工程、调整窗口

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9300383 作者:七十一雾央 新浪微博:http:// ...

最新文章

  1. Intellij-Idea使用小细节
  2. C++中overload,override,overwrite的区别?
  3. [免费]开源制衣公司网站源程序 (三)!
  4. 如何使用功能性JavaScript编写经典游戏Snake并在浏览器中播放-完整的代码示例教程
  5. 改变外观_“改”出来的精彩!盘点5种改变葫芦外观的技艺
  6. 从0开始学习自动化测试框架cypress(四)登录
  7. log4j.properties配置详解(转载)
  8. Shellex:针对shellcode的转换与处理工具
  9. 如何判断网站被黑?网站被黑如何应对?如何防止网站被黑?
  10. 基于Keilv5新建STM32F030工程
  11. [SSL_CHX][2021-08-19]前缀和
  12. 代码读智识  笔墨知人心
  13. Mac版word空格变成小点,多了很多“分节符(下一页)”和“窗体顶端”和“窗体底端”等字样,怎么解决?
  14. 情感分类——Attention(前篇续)
  15. vue组件传参(父传子)
  16. 使用SQL的灵魂(精华)
  17. 搭建quick-cocos2dx调试环境出错
  18. 【已解决】error: conflicting declaration ‘typedef struct LZ4_stream_t LZ4_stream_t’ typedef struct { long
  19. 外泌体相关研究最新进展(2022年6月)
  20. 2019华人儿童教育荣耀盛典圆满落幕

热门文章

  1. win10系统做游戏服务器,Win10专业版如何提升游戏流畅度?Win10游戏流畅度的三种提升方法...
  2. 计算机会计试题原型法的优缺点,《计算机会计学》1..doc
  3. 一种典型的手机APP远程控制PLC解决方案
  4. 程序员美工和真正的游戏美工是两个世界的人
  5. TestBird成为全球最大手游测试平台
  6. Linux桌面环境(桌面系统)大比拼[附带优缺点]
  7. 【逆向工程】C/C++的反汇编表示详解(1)函数调用,栈平衡,变量与参数的内存布局
  8. 零基础入门学Python(十二)—— 魔法方法(下)
  9. 外卖返利系统外卖返利公众号外卖返利源码
  10. 《安卓逆向》查壳工具,权限查询,提取工具