深入浅出之MFC整理-----青铜笔记

来源:互联网 发布:网络电影脱轨 编辑:程序博客网 时间:2024/04/29 16:30
VC中的CAboutDlg
VC中,CAboutDlg,CDrawApp,CDrawDoc,CDrawView和CMainFrame五个类的关系是怎样的?它们各有什么


作用?
CAboutDlg 向导自动生成的"关于..."对话框类.相对独立.
CDrawApp: CWinApp派生类, 一般用于完成一些程序的初始化过程,类似于C中的 main(....)函数.
CDrawDoc, CDrawView: CDocument和CView派生类, 处理各种文档的操作,如:打开,关闭,重载,更新


等.同时和CView及其派生类结合紧密,一起完成对不同文档的处理过程.可以简单理解为 CDocument


类进行实际的工作内容,CView类对CDocument类所做的工作进行绘制,并直观的显示在屏幕上.
CMainFrame: 窗口框架, 一般做为主窗口出现,用来包含各种窗口,处理菜单和工具栏命令.


void CAboutDlg::DoDataExchange这个函数是用来添加消息映射的,说通俗一点就是让你的消息和


消息处理函数之间有机的联系起来。
比如说我要按下按钮是执行某个函数,那么就需要把鼠标按下这个消息,和你的函数之见添加映射




第一篇 勿在浮砂筑高台
1.WIN32程序开发流程:
Windows 程序分为「程序代码」和「UI(User Interface)资源」两大部份,两部份最后以 RC
编译器整合为一个完整的 EXE 档案(图1-1)。所谓 UI 资源是指功能菜单、对话盒
外貌、程序图标、光标形状等等东西。这些 UI 资源的实际内容(二进位码)系借助各
种工具产生,并以各种扩展名存在,如 .ico、.bmp、.cur 等等。程序员必须在一个所谓
的资源描述档(.rc)中描述它们。RC 编译器(RC.EXE)读取 RC 档的描述后将所有 UI
资源档集中制作出一个 .RES 档,再与程序代码结合在一起,这才是一个完整的 Windows
可执行档。
2.需要什么函数库(.LIB):
众所周知 Windows 支持动态联结。换句话说,应用程序所呼叫的 Windows API 函数是
在「执行时期」才联结上的。那么,「联结时期」所需的函数库做什么用?有哪些?
并不是延伸档名为 .dll 者才是动态联结函数库(DLL,Dynamic Link Library),事实
上 .exe、.dll、.fon、.mod、.drv、.ocx 都是所谓的动态联结函数库。
3以消息为基础,以事件为驱动:
Windows 程序的进行系依靠外部发生的事件来驱动。换句话说,程序不断等待(利用一
个 while 回路),等待任何可能的输入,然后做判断,然后再做适当的处理。上述的「输
入」是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中。操作系统
如何捕捉外围设备(如键盘和鼠标)所发生的事件呢?噢,USER 模块掌管各个外围的
驱动程序,它们各有侦测回路。
如果把应用程序获得的各种「输入」分类,可以分为由硬件装置所产生的消息(如鼠标
移动或键盘被按下),放在系统队列(system queue)中,以及由 Windows 系统或其它
Windows 程序传送过来的消息,放在程序队列(application queue)中。以应用程序的眼
光来看,消息就是消息,来自哪里或放在哪里其实并没有太大区别,反正程序呼叫
GetMessage  API 就取得一个消息,程序的生命靠它来推动。所有的 GUI 系统,包括 UNIX
的 X Window 以及 OS/2 的 Presentation Manager,都像这样,是以消息为基础的事件驱
动系统。
4.视窗类别之注册与视窗的产生:
一開始,Windows 程式必須做些初始化工作,為的是產生應用程式的工作舞台:視窗。
這沒有什麼困難,因為API 函数  CreateWindow  完全包辦了整個巨大的工程。但是視窗
產生之前,其屬性必須先設定好。所謂屬性包括視窗的「外貌」和「行為」,一個視窗
的邊框、顏色、標題、位置等等就是其外貌,而視窗接收訊息後的反應就是其行為(具
體地說就是指視窗函数本身)。程式必須在產生視窗之前先利用API 函数  RegisterClass
設定屬性(我們稱此動作為註冊視窗類別)。
5.消息回路:
6.窗口的生命中枢:窗口函数:
7.对话框的运作:
Windows 的对话盒依其与父窗口的关系,分为两类,1. 「令其父窗口除能,直到对话盒结束」,这


种称为 modal 对话盒。
2. 「父窗口与对话盒共同运行」,这种称为 modeless 对话盒。
比较常用的是 modal 对话盒。我就以 Generic 的 ? §About? ¨ 对话盒做为说明范例。
为了做出一个对话盒,程序员必须准备两样东西:
1. 对话盒面板(dialog template)。这是在RC 档中定义的一个对话盒外貌,以各
种方式决定对话盒的大小、字形、内部有哪些控制组件、各在什么位置...等等。
2. 对话盒函数(dialog procedure)。其型态非常类似窗口函数,但是它通常只处
理 WM_INITDIALOG  和 WM _ COMMAND  两个消息。对话盒中的各个控制组件也
都是小小窗口,各有自己的窗口函数,它们以消息与其管理者(父窗口,也就
是对话盒)沟通。而所有的控制组件传来的消息都是  WM _ COMMAND ,再由其
参数分辨哪一种控制组件以及哪一种通告(notification)。
Modal 对话盒的启动与结束,靠的是  DialogBox  和  EndDialog  两个 API 函数。请看
对话盒处理过消息之后,应该传回  TRUE ;如果未处理消息,则应该传回  FALSE 。这是
因为你的对话盒函数之上层还有一个系统提供的预设对话盒函数。如果你传回  FALSE ,
该预设对话盒函数就会接手处理。
8.Windows程序的生与死:
我想你已经了解 Windows 程序的架构以及它与 Windows 系统之间的关系。对
Windows 消息种类以及发生时机的透彻了解,正是程序设计的关键。现在我以窗口的诞
生和死亡,说明消息的发生与传递,以及应用程序的兴起与结束;
a. 程序初始化过程中呼叫  CreateWindow ,为程序建立了一个窗口,做为程序的萤
幕舞台。 CreateWindow  产生窗口之后会送出  WM _ CREATE  直接给窗口函数,
后者于是可以在此时机做些初始化动作(例如配置内存、开文件、读初始资
料...)。
b. 程序活着的过程中,不断以  GetMessage  从消息贮列中抓取消息。如果这个讯
息是 WM _ QUIT , GetMessage  会传回0 而结束 while  回路,进而结束整个程序。
c.  DispatchMessage  透过 Windows USER 模块的协助与监督,把消息分派至窗口
函数。消息将在该处被判别并处理。
d. 程序不断进行 2. 和 3. 的动作。
e. 当使用者按下系统菜单中的Close 命令项,系统送出 WM _ CLOSE 。通常程序
的窗口函数不栏截此消息,于是  DefWindowProc  处理它。
f.  DefWindowProc  收到  WM _ CLOSE  后, 呼叫  DestroyWindow  把窗口清除。
DestroyWindow  本身又会送出  WM _ DESTROY 。
g. 程序对  WM _ DESTROY  的标准反应是呼叫  PostQuitMessage 。
h.  PostQuitMessage  没什么其它动作,就只送出  WM _ QUIT  消息,准备让消息回
路中的  GetMessage  取得,如步骤2 2,结束消息回路。
9.一个进程的诞生与死亡:
执行一个程序,必然就产生一个进程(process)。最直接的程序执行方式就是在 shell (如
Win95 的档案总管或 Windows 3.x 的档案管理员)中以鼠标双击某一个可执行文件图示
(假设其为App.exe),执行起来的 App 进程其实是 shell 呼叫 CreateProcess  启动的。
让我们看看整个流程:
a. shell 呼叫  CreateProcess  启动 App.exe。
b. 系统产生一个「进程核心对象」,计数值为 1。
c. 系统为此进程建立一个 4GB 地址空间。
d. 加载器将必要的码加载到上述地址空间中,包括 App.exe 的程序、数据,以及
所需的动态联结函数库(DLLs)。载入器如何知道要载入哪些 DLLs 呢?它
们被记录在可执行文件(PE 档案格式)的 .idata section 中。
e. 系统为此进程建立一个线程,称为主线程(primary thread)。线程才是
CPU 时间的分配对象。
f. 系统呼叫 C runtime 函数库的 Startup code。
g. Startup code 呼叫App 程序的 WinMain  函数。
h. App 程序开始运作。
i. 使用者关闭App 主窗口,使 WinMain  中的消息回路结束掉,于是 WinMain  结束。
j. 回到 Startup code。
k. 回到系统,系统呼叫  ExitProcess  结束进程。
可以说,透过这种方式执行起来的所有 Windows 程序,都是 shell 的子进程。本来,母
进程与子进程之间可以有某些关系存在,但shell 在呼叫 CreateProcess  时已经把母子之间
的脐带关系剪断了,因此它们事实上是独立个体。
10.一个线程的诞生与死亡:
程序代码的执行,是线程的工作。当一个进程建立起来,主线程也产生。所以每一个
Windows 程序一开始就有了一个线程。我们可以呼叫  CreateThread  产生额外的执行
绪,系统会帮我们完成下列事情:
a. 配置「线程对象」,其 handle 将成为  CreateThread  的传回值。
b. 设定计数值为 1。
c. 配置线程的 context。
d. 保留线程的堆栈。
e. 将 context 中的堆栈指针缓存器(SS)和指令指针缓存器(IP)设定妥当。
看看上面的态势,的确可以显示出线程是CPU 分配时间的单位。所谓工作切换(context
switch)其实就是对线程的 context 的切换。
MFC 六大关键技术之模拟
演化(evolution)永远在进行,
这个世界却不是每天都有革命(revolution)发生。
Application Framework 在软件界确实称得上具有革命精神。
1.MFC 类别阶层:
CCOObbjejecct t
CCMMyyVVieieww
CView
CCMMyyDDoocc
CCDDooccuummeenntt
CCMMyyFFrarammeeWWnndd
CCFFrarammeeWWnndd
CCWWnndd
CCMMyyWWininAApppp
CCWWininAApppp
CCWWininTThhrereaadd
CCCCmmddTTaargrgeett
Frame1 并没有  new  任何物件,反倒是有一个全域对象  theApp  存在。
C++ 规定,全域对象的建构将比程序进入点(在DOS 环境为 main ,在Windows 环境为
WinMain )更早。所以  theApp  的建构式将更早于  main 。换句话说你所看到的执行结果
中的那些建构式输出动作全都是在 main  函数之前完成的。
2.MFC 程序的初始化:
MFC 程序也是个 Windows 程序,它的内部一定也像第1章所述一样,有窗口注册动
作,有窗口产生动作,有消息回路动作,也有窗口函数。此刻我并不打算做出Windows 程
式,只是想交待给你一个程序流程,这个流程正是任何MFC 程序的初始化过程的简化。
就如我曾在第1章解释过的, InitApplication  和  InitInstance  现在成了 MFC 的  CWinApp
的两个虚函数。前者负责「每一个程序只做一次」的动作,后者负责「每一个执行个体都得做一次


」的动作。通常,系统会(并且有能力)为你注册一些标准的窗口类别(当
然也就准备好了一些标准的窗口函数),你(应用程序设计者)应该在你的  CMyWinApp
中改写  InitInstance ,并在其中把窗口产生出来 -- 这样你才有机会在标准的窗口类别中
指定自己的窗口标题和菜单。
3.pWnd -> WindowProc  究竟是呼叫哪一个函数?不一定,得视 pWnd  到底指向何种类别之
对象而定 -- 别忘了 WindowProc  是虚函数。这正是虚函数发挥它功效的地方呀:
? 如果 pWnd  指向 CMyFrameWnd  对象,那么呼叫的是 CFrameWnd :: WindowProc 。
而因为 CFrameWnd  并没有改写 WindowProc  , 所以呼叫的其实是
CWnd :: WindowProc 。
? 如果 pWnd  指向 CMyView  对象,那么呼叫的是 CView :: WindowProc 。而因为 CView
并没有改写 WindowProc ,所以呼叫的其实是 CWnd :: WindowProc 。
虽然殊途同归,意义上是不相同的。切记!切记!
CWnd :: WindowProc  首先判断消息是否为  WM _ COMMAND 。如果不是,事情最单纯,
就把消息往父类别推去,父类别再往祖父类别推去。每到一个类别的消息映像表,原本
应该比对  AFX_MSGMAP_ENTRY  的每一个元素,比对成功就呼叫对应的处理例程。不
过在这里我不作比对,只是把 AFX_MSGMAP_ENTRY  中的类别识别代码印出来(就像上
一节的 Frame7 程序一样),以表示「到此一游」:
如果消息是 WM _ COMMAND , CWnd :: WindowProc  呼叫  _ OnCommand 。好,注意了,
这又是一个 CWnd  的虚函数:
a. 如果 this  指向  CMyFrameWnd  对象,那么呼叫的是  CFrameWnd :: OnCommand 。
b. 如果 this  指向 CMyView  对象,那么呼叫的是 CView :: OnCommand 。而因为 CView
并没有改写  OnCommand ,所以呼叫的其实是  CWnd :: OnCommand 。
这次可就没有殊途同归了。
又一次遭遇虚函数。经过前两次的分析,相信你对此很有经验了。 _ OnCmdMsg  是
CCmdTarget  的虚函数,所以:
a. 如果 this  指向 CMyFrameWnd  对象,那么呼叫的是 CFrameWnd :: OnCmdMsg 。
b. 如果 this  指向 CMyView  对象,那么呼叫的是 CView :: OnCmdMsg 。
c. 如果 this  指向 CMyDoc  对象,那么呼叫的是 CDocument :: OnCmdMsg 。
d. 如果 this  指向 CMyWinApp  对象,那么呼叫的是 CWinApp :: OnCmdMsg 。而因为
CWinApp  并没有改写 OnCmdMsg ,所以呼叫的其实是 CCmdTarget :: OnCmdMsg 。
目前的情况是第一种,于是呼叫CFrameWnd::OnCmdMsg:
第二篇 欲善其事先利其器
强壮的弓,让箭飞得更远
1.程序代码产生器:AppWizard
图4-8 Scribble 程序的 readme.txt 檔。
别忘了,AppWizard 产生的是化学反应而不是物理反应,是不能够还原的。我们很容易
犯的错误是像进入糖果店里的小孩
图4-8 Scribble 程序的 readme.txt 檔。
别忘了,AppWizard 产生的是化学反应而不是物理反应,是不能够还原的。我们很容易
犯的错误是像进入糖果店里的小孩 一 样,每样东西都想要。你应该约束自己,因为错 一
步已是百年身,不能稍后又回到 AppWizard 要求去掉或改变某些选项,例如想把 SDI 改
为 MDI 或是想增加 OLE 支持等等,都不能够。欲变更程序,只有两条路可走:要不
就令 AppWizard 重新产生
步已是百年身,不能稍后又回到 AppWizard 要求去掉或改变某些选项,例如想把 SDI 改
为 MDI 或是想增加 OLE 支持等等,都不能够。欲变更程序,只有两条路可走:要不
就令 AppWizard 重新产生 一 组新的程序骨干,然后回到原程序 中打捞点什么可以用的,
以 Copy/Paste 方式移植过来;要不就是直接进入原来程序修修补补。至于修补过程
打捞点什么可以用的,
以 Copy/Paste 方式移植过来;要不就是直接进入原来程序修修补补。至于修补过程 中到
底会多么令
底会多么令 人 厌烦,那就不 一而论了。所以,在开始你的程序撰写之前,小心做好系统
分析的工作。


Application Framework 将成为软件技术
323
Application Framework 将成为软件技术 中 最重要的 一环。如果你不知道它是什么,赶快
学习它;如果你还没有使用它,赶快开始用。机会之窗不会永远为你打开,在你的竞争
者把它关闭之前赶快进入!如果你认为改朝换代还早得很,请注意两件事情。第
环。如果你不知道它是什么,赶快
学习它;如果你还没有使用它,赶快开始用。机会之窗不会永远为你打开,在你的竞争
者把它关闭之前赶快进入!如果你认为改朝换代还早得很,请注意两件事情。第 一,江
山什么时候变色可谁也料不准,当你埋首工作时,外面的世界进步尤其飞快;第
,江
山什么时候变色可谁也料不准,当你埋首工作时,外面的世界进步尤其飞快;第 二,物
件导向和 Application Framework 可不是那么容易学的,花多少时间才能登堂入室可还得
凭各
,物
件导向和 Application Framework 可不是那么容易学的,花多少时间才能登堂入室可还得
凭各 人资质和基础呢。
MFC 数据型态(data types)
BOOL Boolean 值(布尔值,不是 TRUE 就是 FALSE)
BSTR 32-bit 字符指针
BYTE 8-bit 整数,未带正负号
COLORREF 32-bit 数值,代表
BYTE 8-bit 整数,未带正负号
COLORREF 32-bit 数值,代表 一个颜色值
DWORD 32-bit 整数,未带正负号
LONG 32-bit 整数,带正负号
LPARAM 32-bit 数值,做为窗口函数或callback 函数的
个颜色值
DWORD 32-bit 整数,未带正负号
LONG 32-bit 整数,带正负号
LPARAM 32-bit 数值,做为窗口函数或callback 函数的 一个参数
LPCSTR 32-bit 指针,指向
个参数
LPCSTR 32-bit 指针,指向 一个常数字符串
LPSTR 32-bit 指针,指向
个常数字符串
LPSTR 32-bit 指针,指向 一个字符串
LPCTSTR 32-bit 指针,指向
个字符串
LPCTSTR 32-bit 指针,指向 一个常数字符串。此字符串可移植到 Unicode 和 DBCS(双
字节字集)
LPTSTR 32-bit 指针,指向
个常数字符串。此字符串可移植到 Unicode 和 DBCS(双
字节字集)
LPTSTR 32-bit 指针,指向 一个字符串。此字符串可移植到 Unicode 和 DBCS(双位
组字集)
LPVOID 32-bit 指针,指向
个字符串。此字符串可移植到 Unicode 和 DBCS(双位
组字集)
LPVOID 32-bit 指针,指向 一个未指定型态的数据
LPRESULT 32-bit 数值,做为窗口函数或 callback 函数的回返值
UINT 在 Win16
个未指定型态的数据
LPRESULT 32-bit 数值,做为窗口函数或 callback 函数的回返值
UINT 在 Win16  中 是 一 个 16-bit 未带正负号整数,在 Win32  中 是 一个 32-bit
未带正负号整数。
WNDPROC 32-bit 指针,指向
个 32-bit
未带正负号整数。
WNDPROC 32-bit 指针,指向 一个窗口函数
WORD 16-bit 整数,未带正负号
MFC 程序的生死因果:
理想如果不向实际做点妥协,理想就会归于尘土。
不二法门:熟记MFC 类别的阶层架构
看不到  WinMain ,因此不知程序从哪里开始执行。
? 看不到
,因此不知程序从哪里开始执行。
? 看不到  RegisterClass  和  CreateWindow ,那么窗口是如何做出来的呢?
? 看不到 Message Loop(
,那么窗口是如何做出来的呢?
? 看不到 Message Loop( GetMessage / / DispatchMessage ),那么程序如何推动?
? 看不到 Window Procedure,那么窗口如何运作?
5 BOOL InitInstance(); // 每
#0006 // 作者 : 侯俊杰
#0007 // 编译联结 : 请参考 hello.mak
#0008 //
#0009 // 宣告 Hello 程序的两个类别 : CMyWinApp 和 CMyFrameWnd
#0010 //---------------------------------------------------------------
#0011
#0012 class CMyWinApp : public CWinApp
#0013 {
#0014 public:
#0015 BOOL InitInstance(); // 每 一个应用程序都应该改写此函数


MFC 程序的来龙去脉:


第 一件事情就是找出 MFC 程序的进入点。MFC 程序也是 Windows 程序,所以它应该
也有 一 个  WinMain ,但是我们在 Hello 程序看不到它的踪影。是的,但先别急,在程序
进入点之前,更有 一 个(而且仅有 一 个)全域对象(本例名为 theApp ),这是所谓的
application object,当操作系统将程序加载并启动,这个全域对象获得配置,其建构式会
先执行,比  WinMain  更早。所以以时间顺序来说,我们先看看这个 application object。




几乎可以说 CWinApp  用来取代 WinMain  在SDK 程序 中 的 地位。这并不是说MFC 程序
没有 WinMain (稍后我会解释),而是说传统 上  SDK 程序的 WinMain  所完成的工作现在由
在由  CWinApp  的 三 个函数完成:
virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int Run();


当你执行 Hello,这个全域对象产生,于是建构式执行起来。我们
并没有定义  CMyWinApp  建构式;至于其父类别  CWinApp  的建构式内容摘要如 下






CWinApp  之 中 的成员变量将因为  theApp  这个全域对象的诞生而获得配置与初值。如果
程序 中 没有 theApp  存在,编译联结还是可以顺利通过,但执行时会出现系统错误消息 :


再根据稍早所述  CWinApp :: CWinApp  中 的动作,我们于是知道, AfxGetApp  其实就是取
得 CMyWinApp  对象指针。所以, AfxWinMain  中 这样的动作:
CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();
CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();
其实就相当于呼叫:
CMyWinApp::InitApplication();
CMyWinApp::InitInstance();
CMyWinApp::Run();
CMyWinApp::InitApplication();
CMyWinApp::InitInstance();
CMyWinApp::Run();
因而导至呼叫:
CWinApp::InitApplication(); // 因为 CMyWinApp 并没有改写 InitApplication
CMyWinApp::InitInstance(); // 因为 CMyWinApp 改写了 InitInstance
CWinApp::Run(); // 因为 CMyWinApp 并没有改写Run


 产生主窗口(并先注册窗口类别)
根据第1章 SDK 程序设计的经验推测, InitApplication  应该是注册窗口类别的场所?
InitInstance  应该是产生窗口并显示窗口的场所? Run  应该是攫取消息并分派消息的场
所?有对有错!以 下 数节我将实际带你看看 MFC 的原始码,如此 一来就可以了解隐藏
在 MFC 背后的玄妙了。我的终极目标并不在MFC 原始码(虽然那的确是学习设计 一个
application framework 的好教材),我只是想拿把刀子把MFC 看似朦胧的内部运作来个
大解剖,挑出其经脉;有这种扎实的根基,使用MFC 才能知其然并知其所以然。 下面小
节分别讨论  AfxWinMain  的 四 个主要动作以及引发的行为。




但是你要知道, CMyWinApp  继承自  CWinApp ,而  InitApplication  又是  CWinApp  的 一个
虚函数;我们并没有改写它(大部份情况 下 不需改写它),所以 上 述动作相当于呼叫:
CWinApp::InitApplication();




继 InitApplication  之后, AfxWinMain  呼叫 pApp -> InitInstance 。稍早我说过了, pApp  


指向CMyWinApp  对象(也就是本例的  theApp ),所以,当程序呼叫:App->InitInstance();当


于呼叫MyWinApp::InitInstance();但是你要知道, CMyWinApp  继承自  CWinApp ,nitInstance  


又是  CWinApp  的 一个虚拟函数。由于我们改写了它,所以 上 述动作的的确确就是呼叫我们自


己( CMyWinApp )的这个  InitInstance  函数。我们将在该处展开我们的主窗口生命。


一般而言,CMyWinApp 只改写
CWinApp 中的 InitInstance,通常
它不改写 InitApplication 和 Run。
overridden注意:应用程序 一 定要改写虚函数 InitInstance ,因为它在 CWinApp  中只是个空


函数,没有任何内建(预设)动作。


把消息与处理函数串接在一起:Message Map 机制


基本 上 Message Map 机制是为了提供更方便的程序接口(例如宏或表格),让程序员
很方便就可以建立起消息与处理例程的对应关系。


这么 一 来就把消息  WM _ _ PAINT  导到  OnPaint  函数, 把  WM _ _ COMMAND
( IDM _ _ ABOUT )导到 OnAbout 函数去了。但是,单凭 一 个  ON_WM_PAINT  宏,没
有任何参数,如何使 WM _ _ PAINT  流到  OnPaint  函数呢?
MFC 把消息主要分为 三 大类,Message Map 机制 中对于消息与函数间的对映关系也明定
以下三种 :? 标准Windows 消息(WM_xxx)的对映规则:






消息流动是个颇为复杂的机制,它和Document/View、动态生成(Dynamic Creation),
档案读写(Serialization) 一 样,都是需要特别留心的 地 方。


*来龙去脉总整理:
Application object 产生,内存于是获得配置,初值亦设立了。
?  AfxWinMain  执行  AfxWinInit ,后者又呼叫  AfxInitThread ,把消息队列尽量加大到
fxWinMain  执行  InitApplication 。这是  CWinApp  的虚函数,但我们通常不改写它。


AfxWinMain  执行 InitInstance 。这是 CWinApp  的虚函数,我们必须改写它。
 CMyWinApp :: InitInstance 'new'  了 一 个  CMyFrameWnd  物件。
?  CMyFrameWnd  建构式呼叫  Create ,产生主窗口。我们在  Create  参数 中指定的
窗口类别是  NULL , 于是 MFC 根据窗口种类, 自行为我们注册 一个名为
"AfxFrameOrView42d" 的窗口类别。




回到  AfxWinMain ,执行  Run ,进入消息循环。
? 程序获得  WM _ _ PAINT  消息(藉由  CWinApp :: Run  中 的 :: GetMessage  回路)。
?
回路)。
?  WM _ _ PAINT  经由:: DispatchMessage  送到窗口函数 CWnd :: DefWindowProc  中 。




? 预设函数对于 WM _ _ CLOSE  的处理方式是呼叫 :: DestroyWindow , 并因而发出
WM _ _ DESTROY 。
预设之 WM _ _ DESTROY  处理方式是呼叫:: PostQuitMessage ,因此发出 WM _ _ QUIT 。
?。?  CWinApp :: Run  收到 WM _ _ QUIT  后会结束其内部之消息回路, 然后呼叫
ExitInstance ,这是 CWinApp  的 一个虚函数。


? 如果 CMyWinApp  改写了 ExitInstance  , 那么 CWinApp :: Run  所呼叫的就是
CMyWinApp :: ExitInstance ,否则就是  CWinApp :: ExitInstance 。
? 最后回到  AfxWinMain ,执行  AfxWinTerm ,结束程序。




通用对话盒(Common Dialogs):




本章回顾
乍看 MFC 应用程序代码,实在很难推想程序的进行。 一 开始是 一 个衍生自  CWinApp  的全
域对象 application object,然后是 一 个隐藏的  WinMain  函数,呼叫application object 的
InitInstance  函数,将程序初始化。初始化动作包括建构 一 个窗口对象( CFrameWnd  物
件),而其建构式又呼叫 CFrameWnd :: Create 产生真正的窗口(并在产生之前要求 MFC
注册窗口类别)。窗口产生后  WinMain  又呼叫  Run  启动消息回路,将  WM _ _ COMMAND
( IDM _ _ ABOUT )和  WM _ _ PAINT  分别交给成员函数  OnAbout  和  OnPaint  处理。
虽然刨根究底不易,但是我们都同意,MFC 应用程序代码的确比 SDK 应用程序代码精简许多。


简单而完整:MFC 骨干程序:
当技术愈来愈复杂,
入门愈来愈困难,
我们的困惑愈来愈深,
犹豫愈来愈多。


不二法门:熟记MFC 类别阶层架构:
1.theApp;
2.AfxWinInit(?K);
3.pApp->InitApplication();
4.pApp->InitInstance();
5.InitInstance();
6.CMainFrame* pMainFrame = new CMainFrame;
7.pMainFrame->LoadFrame(IDR_MAINFRAME);
8.OnCreate(LPCREATESTRUCT lpCreateStruct)
9.pMainFrame->ShowWindow(m_nCmdShow);




以 下 是图7-6 程序流程之说明:
? ~ ~ ? 动作与流程和前 一 章的Hello 程序如出 一 辙。
? 我们改写  InitInstance  这个虚函数。
? new  一 个  CMultiDocTemplate  对象,此对象规划 Document、View 以及 DocumentFrame 窗


口对象,此对象规划 Document、View 以及 DocumentFrame 窗口 三 者之关系。
? new  一 个  CMyMDIFrameWnd  对象,做为主窗口对象。
? 呼叫  LoadFrame ,产生主窗口并加挂菜单等诸元,并指定窗口标题、文件标题、文件档等(关


键在IDR_MAINFRAME 常数)。 LoadFrame  内部将呼叫  Create ,后者将呼叫  CreateWindowEx 


,于是触发  WM _ _ CREATE  消息。
? 由于我们曾于 CMainFrame  之 中 拦截 WM _ _ CREATE (利用 ON_WM_CREATE  宏),
所以 WM _ _ CREATE  产生之际 Framework 会呼叫  OnCreate 。我们在此为主窗口挂 上工具列。
? 回到  InitInstance ,执行  ShowWindow  显示窗口。
InitInstance  结束,回到  AfxWinMain ,执行  Run ,进入消息循环。





0 0
原创粉丝点击