3.1 MFC概述
2009年12月01日
Microsoft提供的MFC是放置Windows API的面向对象封装的C++类库。MFC (Microsoft Foundation Class)6.0版本封装了大约200个类,其中一些我们可以直接使用,而另一些则主要作为我们自己的类的基础类。一些MFC类极其简单,例如CPoint类,它代表一个点(一个由x和y坐标定义的位置)。有些类比较复杂,例如CWnd类,它封装了窗口的功能。在MFC程序中,我们并不经常直接调用Windows API;而是从MFC类创建对象并调用属于这些对象的成员函数。本章主要介绍的是MFC的概念及一些编程的基础。
3.1 MFC概述
MFC(Microsoft Foundation Class)是一个应用程序的框架结构。MFC不仅仅是一个类集合,它还帮助定义了应用程序的结构并为应用程序处理许多杂务。MFC中的各种类结合起来构成了的应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法。因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作。
(1)AppWizard可以用来生成初步的框架文件(代码和资源等)。
(2)资源编辑器用于帮助直观地设计用户接口。
(3)ClassWizard用来协助添加代码到框架文件。
(4)最后,经过编译,通过类库实现了应用程序特定的逻辑。
3.1.1 MFC编程的特点
曾经使用过传统的Windows编程方法开发应用程序的读者,就会深刻地体会到,即使是开发一个简单的Windows应用程序也需要对Windows的编程原理有很深刻的认识,同时也要手工编写很多的代码。而且程序的出错率几乎是随着代码长度的增加呈几何级数增长的,这就使得调试程序变得非常困难。所以传统的Windows编程是需要极大的耐心和丰富的编程经验的。
近几年来,面向对象技术无论是在理论还是实践上都在飞速地发展。面向对象技术中最重要的就是“对象”的概念,它把现实世界中的气球、自行车等客观实体抽象成程序中的“对象”。这种“对象”具有一定的属性和方法,这里的属性指对象本身的各种特性参数。如气球的体积,自行车的长度等,而方法是指对象本身所能执行的功能,如气球能飞,自行车能走动等。一个具体的对象可以有许多的属性和方法,面向对象技术的重要特点就是对象的封装性,对于外界而言,并不需要知道对象有哪些属性,也不需要知道对象本身的方法是如何实现的,而只需要调用对象所提供的方法来完成特定的功能。从这里我们可以看出,当把面向对象技术应用到程序设计中时,程序员只是在编写对象方法时才需要关心对象本身的细节问题,大部分的时间是放在对对象的方法的调用上,组织这些对象进行协同工作。
MFC的本质就是一个包含了许多微软公司已经定义好的对象的类库,我们知道,虽然我们要编写的程序在功能上是千差万别的,但从本质上来讲,都可以化归为用户界面的设计,对文件的操作,多媒体的使用,数据库的访问等一些最主要的方面。这一点正是微软提供MFC类库最重要的原因,在这个类库中包含了一百多个程序开发过程中最常用到的对象。在进行程序设计的时候,如果类库中的某个对象能完成所需要的功能,这时我们只要简单地调用已有对象的方法就可以了。我们还可以利用面向对象技术中很重要的“继承”方法从类库中的已有对象派生出我们自己的对象,这时派生出来的对象除了具有类库中的对象的特性和功能之外,还可以由我们自己根据需要加上所需的特性和方法,产生一个更专门的,功能更为强大的对象。当然,也可以在程序中创建全新的对象,并根据需要不断完善对象的功能。
正是由于MFC编程方法充分利用了面向对象技术的优点,它使得我们编程时极少需要关心对象方法的实现细节,同时类库中的各种对象的强大功能足以完成我们程序中的绝大部分所需功能,这使得应用程序中程序员所需要编写的代码大为减少,有力地保证了程序的良好的可调试性。
最后要指出的是MFC类库在提供的对象的各种属性和方法都经过谨慎的编写和严格的测试,可靠性很高,这就保证了使用MFC类库不会影响程序的可靠性和正确性。
3.1.2 MFC的设计思想
在Microsoft的程序员开始创建MFC时,他们对未来的看法包括以下几个设计目标。
(1)MFC应该给Windows操作系统提供一个面向对象的接口,支持可重用性、自包含性及其他OOP原则。
(2)实现上述目标的前提是不需要强加给系统过多的工作或不增加应用程序对内存的不必要的开销。
第一个目标的实现可通过编写类来封装窗口、对话框及其他对象,并引入某些关键的虚函数来完成。第二个目标要求MFC设计人员尽早就如何将窗口、菜单及其他对象被MFC(如CWnd和CMenu)包装作为选择。
MFC设计者所用的使类库带来的总开销减到最小的方法之一是在MFC对象与Windows对象之间的关系中得到了体现。在Windows中,有关窗口特性和目前状态的信息被保存在操作系统拥有的内存中。这些信息对应用程序是隐藏的,应用程序只能处理窗口句柄或HWND。MFC并没有复制在CWnd类的数据成员中的与HWND有关的所有信息,事实上,MFC通过将HWND存储在称为m_hWnd的公用CWnd数据成员中,而在CWnd内包装了一个窗口。作为规则,如果Windows通过某些类型的句柄展示一个对象,那么相应的MFC类就会包含那个句柄的数据成员。如果我们想要调用API函数,该函数要求一个句柄,但是我们只有CWnd或CWnd指针,而不是HWND,那么这些知识对于我们来说是有用的。
3.1.3 MFC类的分层结构
MFC提供了许多设计好的类来满足广泛的需要。大多数MFC类都是从CObject中直接或间接地派生出来的。CObject给那些继承它的类提供了3个重要的特性:
― 串行化支持
― 运行时类信息支持
― 诊断和调试支持
串行化是对象的永久数据流出或流入存储介质(如磁盘文件)的进程。把CObject作为基类,可以创建可串行化的类,其实例容易存储和重新创建。运行的类信息(RTCI)允许我们在运行时检索对象的类名称及对象的其他信息。RTCI的执行不同于C++中的运行时类型信息(RTTI)机制,因为它比RTTI要早出现好多年。嵌入在CObject中的诊断和调试支持允许我们对CObject派生类的实例执行有效性检查,并将状态信息转储到一个调试窗口。
CObject对它的派生类还提供了别的好处。例如,重载new和delete运算符防止内存泄漏。如果从CObject派生类创建了一个对象,而没有在应用程序结束前删除它,那么MFC将会在调试输出窗口写一条警告信息。随着我们对MFC越来越熟悉,这个MFC类基本特点的重要性也会变得越来越清晰。
3.1.4 MFC程序结构分析
MFC作为微软应用程序的框架,举足轻重。MFC是C++的Win32 API。更重要的是,它提供了文档/视图框架;sdk编程中的 wndproc也变成了方便的消息映射;令人恐惧的设计精妙的宏。
1.Windows程序工作原理
Windows程序设计是一种完全不同于传统的DOS方式的程序设计方法,它是一种事件驱动方式的程序设计模式。在程序提供给用户的界面中有许多可操作的可视对象。用户从所有可能的操作中任意选择,被选择的操作会产生某些特定的事件,这些事件发生后的结果是向程序中的某些对象发出消息,然后这些对象调用相应的消息处理函数来完成特定的操作。Windows应用程序的最大特点就是程序没有固定的流程,而只是针对某个事件的处理有特定的子流程,Windows应用程序就是由许多这样的子流程构成的。
从上面的讨论中可以看出,Windows应用程序在本质上是面向对象的。程序提供给用户界面的可视对象在程序的内部一般也是一个对象,用户对可视对象的操作通过事件驱动模式触发相应对象的可用方法。程序的运行过程就是用户的外部操作不断产生事件,这些事件又被相应的对象处理的过程。
2.建立应用程序
在“Build”菜单中选择“Rebuild All”菜单项,系统开始编译由AppWizard自动生成的程序框架中所有文件中的源代码,并且链接生成可执行的应用程序。按“Ctrl+F5”,应用程序就开始运行了,虽然我们没有编写一行代码,但是可以看出由系统自动生成的应用程序的界面已经有了一个标准Windows应用程序所需的几个组成部分,我们要做的事情是往这个应用程序中添加必要的代码以完成我们所需要的功能。
接下来将要来介绍Windows下自动生成的这个应用程序框架,让我们对MFC方式的Windows应用程序的工作原理有全面的认识,只有这样才知道应该如何往程序框架当中添加需要的代码。
3.程序结构剖析
使用MFC方式的应用程序的4个主要类之间的关系。
CMYAPP类主要的作用是用来处理消息的,它统一管理程序收到的所有的消息,然后把消息分配到相应的对象。CMAINFRAME是CMYVIEW的父类,也就是说视窗VIEW显示在主框窗MAINFRAME的客户区中。类CMYVIEW的作用是显示数据,而数据的来源是类CMYDOC,在MFC程序中,程序的数据是放在文档当中的,而显示数据则是利用视窗方式,文档与视窗分离带来的好处就是一个文档可以同时具有多个视窗,每个视窗只显示文档中的一部分数据,或者以特定的风格显示文档中的数据。文档与视窗分离的另一个好处就是在程序中可以处理多个文档,通过对不同的视窗的处理达到对不同的文档分别处理的目的。
使用过传统的Windows编程方法的人都知道,在应用程序中有一个重要的函数WinMain(),这个函数是应用程序的基础,用户操作所产生的消息正是经过这个函数的处理派送到对应的对象中进行处理的。在MFC方式的Windows应用程序中,用来处理消息的是系统自动生成的MFC中的类CWINAPP的派生类CMYAPP,下面就从这个类开始介绍应用程序的框架。
(1)类CMYAPP
类CMYAPP是应用程序运行的基础,这个类是由MFC中的类CWINAPP派生来的。在这个类中除了有一般类都有的构造函数,一个重要的成员函数就是INITINSTANCE,我们知道,在Windows环境下面可以运行同一程序的多个实例,函数INITINSTANCE的作用就是在生成一个新的实例的时候,完成一些初始化的工作。
下面研究INITINSTANCE函数所做的事情,我们开始定义了一个文档模板对象指针PDOCTEMPLATE,通过NEW操作符,系统动态生成了这个文档模板对象,然后使用ADDDOCTEMPLATE函数把这个文档模板对象加入到应用程序所维护的文档模板链表当中,这个文档模板PDOCTEMPLATE的作用就是把程序用到的框架窗口,CMAINFRAME,文档CMYDOC,视窗CMYVIEW与应用对象CMYAPP联系起来。
CMYAPP类提供了用户与Windows应用程序之间进行交流的界面。在生成这个类的对象后,这个对象自动地把自身与Windows系统建立联系,接收Windows传送的消息,并交给程序中相应的对象去处理,这就免去了程序员许多的工作,使得开发C++的Windows程序变得简单方便。
(2)类CMAINFRAME
类CMAINFRAME是由MFC中的CFRAMEWND派生来的,所以它也是一个框架窗口。前面已经指出,CMAINFRAME是类CMYVIEW的父类,也就是说CMYVIEW类的对象显示在主框架窗口的客户区中。在类CMAINFRAME中,系统已经从类CFRAMEWND那里继承了处理窗口的一般事件的Windows消息,比如改变窗口的大小,窗口最小化等的成员函数,因此编程的时候程序员不需要再关心此类消息的处理,从而减轻了程序员的负担。当然,如果确实需要重新编写处理此类消息的成员函数,则需要对原有的成员函数进行重载。
在MFC程序中,我们并不需要经常对CMAINFRAME类进行操作,更多的是对视窗类进行操作,达到对程序中的数据进行编辑和修改的目的。
最后要指出的是,在MFC方式的程序中,当程序的一个实例被运行的时候,系统根据前面在CMYAPP类中介绍的文档模板对象自动生成类CMAINFRAME,CMYVIEW,CMYDOC的对象,而不需要程序员主动地去创建这些类的对象。
(3)类CMYVIEW与CMYDOC
之所以把CMYVIEW类和CMYDOC类放一起来介绍,是因为这两个类是密切相关的,文档是由文档模板对象生成的,并由应用程序对象管理,而用户则是通过与文档相联系的视窗对象来存储、管理应用程序的数据,用户与文档之间的交互则是通过与文档相关联的视窗对象来进行的。
生成一个新的文档的时候,MFC程序同时生成一个框架窗口,并且在框架窗口的客户区中生成一个视窗对象作为框架窗口的子窗口,这个子窗口以可视化的方式表现文档中的内容。视窗的重要功能就是负责处理用户的鼠标、键盘等操作,通过对视窗对象的处理达到处理文档对象的目的。
要指出的一点是,Windows应用程序分单文档界面SDI和多文档界面MDI两种,在单文档界面中,文档窗口与主框架窗口是同一概念。而这时的视窗对象则是显示在文档窗口的客户区当中。前面生成的test程序使用的就是单文档界面方式,此时文档窗口是主框架窗口,即类CMAINFRAME的对象。
3.1.5 MFC中的WinMain函数
WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束。WinMain函数的原型如下:
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的。第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数。第二个参数hPrevInstance表示当前实例的前一个实例的句柄。通过查看MSDN我们可以知道,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。
3.1.6 MFC库消息映射
MFC库应用框架并没有采用虚函数来处理Windows消息,相反,它通过一些宏来将特定的消息映射到派生类中相应的成员函数。库类应用框架没有采用虚函数,这主要是因为以下原因:库类中包含了5个窗口类,分别和Windows的5种窗口类型相对应,如果采用虚函数的办法来处理消息,那么每个窗口基类就要对140条消息分别定义一个虚函数。C++对每个虚函数都要求有一个4字节的传递结构,称做“vtable”,无论该虚函数是否在派生类中被重新定义,“vtable”都必不可少。这样,对于每个特定类型的窗口或控件,应用都需要一个2.8KB大小的表来支持虚消息控制函数。
那么对于菜单命令消息及按钮命令消息来说,设计消息控制函数不能将它们定义成窗口基类中的虚函数,因为不同的应用会有不同的菜单项和按钮。MFC库的这种消息映射系统就避免了使用大的vtable表,并且能够处理各种各样应用的命令消息。这种体制也允许某些非窗口类(如文档类和应用类)来控制命令消息。这种消息映射体制同Borland作为OWL的一个组成部分所提供的“动态传递表”系统也有所不同,它不需要C++作任何扩展。
MFC库消息控制函数要求我们提供函数原型、函数体,以及在消息映射中的入口,而ClassWizard会帮助我们将消息控制函数引入我们所设计的类中,只要我们从列表框中选择一个Windows消息ID,ClassWizard就会自动产生具有正确参数及返回值的代码。
2009年12月01日
Microsoft提供的MFC是放置Windows API的面向对象封装的C++类库。MFC (Microsoft Foundation Class)6.0版本封装了大约200个类,其中一些我们可以直接使用,而另一些则主要作为我们自己的类的基础类。一些MFC类极其简单,例如CPoint类,它代表一个点(一个由x和y坐标定义的位置)。有些类比较复杂,例如CWnd类,它封装了窗口的功能。在MFC程序中,我们并不经常直接调用Windows API;而是从MFC类创建对象并调用属于这些对象的成员函数。本章主要介绍的是MFC的概念及一些编程的基础。
3.1 MFC概述
MFC(Microsoft Foundation Class)是一个应用程序的框架结构。MFC不仅仅是一个类集合,它还帮助定义了应用程序的结构并为应用程序处理许多杂务。MFC中的各种类结合起来构成了的应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法。因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作。
(1)AppWizard可以用来生成初步的框架文件(代码和资源等)。
(2)资源编辑器用于帮助直观地设计用户接口。
(3)ClassWizard用来协助添加代码到框架文件。
(4)最后,经过编译,通过类库实现了应用程序特定的逻辑。
3.1.1 MFC编程的特点
曾经使用过传统的Windows编程方法开发应用程序的读者,就会深刻地体会到,即使是开发一个简单的Windows应用程序也需要对Windows的编程原理有很深刻的认识,同时也要手工编写很多的代码。而且程序的出错率几乎是随着代码长度的增加呈几何级数增长的,这就使得调试程序变得非常困难。所以传统的Windows编程是需要极大的耐心和丰富的编程经验的。
近几年来,面向对象技术无论是在理论还是实践上都在飞速地发展。面向对象技术中最重要的就是“对象”的概念,它把现实世界中的气球、自行车等客观实体抽象成程序中的“对象”。这种“对象”具有一定的属性和方法,这里的属性指对象本身的各种特性参数。如气球的体积,自行车的长度等,而方法是指对象本身所能执行的功能,如气球能飞,自行车能走动等。一个具体的对象可以有许多的属性和方法,面向对象技术的重要特点就是对象的封装性,对于外界而言,并不需要知道对象有哪些属性,也不需要知道对象本身的方法是如何实现的,而只需要调用对象所提供的方法来完成特定的功能。从这里我们可以看出,当把面向对象技术应用到程序设计中时,程序员只是在编写对象方法时才需要关心对象本身的细节问题,大部分的时间是放在对对象的方法的调用上,组织这些对象进行协同工作。
MFC的本质就是一个包含了许多微软公司已经定义好的对象的类库,我们知道,虽然我们要编写的程序在功能上是千差万别的,但从本质上来讲,都可以化归为用户界面的设计,对文件的操作,多媒体的使用,数据库的访问等一些最主要的方面。这一点正是微软提供MFC类库最重要的原因,在这个类库中包含了一百多个程序开发过程中最常用到的对象。在进行程序设计的时候,如果类库中的某个对象能完成所需要的功能,这时我们只要简单地调用已有对象的方法就可以了。我们还可以利用面向对象技术中很重要的“继承”方法从类库中的已有对象派生出我们自己的对象,这时派生出来的对象除了具有类库中的对象的特性和功能之外,还可以由我们自己根据需要加上所需的特性和方法,产生一个更专门的,功能更为强大的对象。当然,也可以在程序中创建全新的对象,并根据需要不断完善对象的功能。
正是由于MFC编程方法充分利用了面向对象技术的优点,它使得我们编程时极少需要关心对象方法的实现细节,同时类库中的各种对象的强大功能足以完成我们程序中的绝大部分所需功能,这使得应用程序中程序员所需要编写的代码大为减少,有力地保证了程序的良好的可调试性。
最后要指出的是MFC类库在提供的对象的各种属性和方法都经过谨慎的编写和严格的测试,可靠性很高,这就保证了使用MFC类库不会影响程序的可靠性和正确性。
3.1.2 MFC的设计思想
在Microsoft的程序员开始创建MFC时,他们对未来的看法包括以下几个设计目标。
(1)MFC应该给Windows操作系统提供一个面向对象的接口,支持可重用性、自包含性及其他OOP原则。
(2)实现上述目标的前提是不需要强加给系统过多的工作或不增加应用程序对内存的不必要的开销。
第一个目标的实现可通过编写类来封装窗口、对话框及其他对象,并引入某些关键的虚函数来完成。第二个目标要求MFC设计人员尽早就如何将窗口、菜单及其他对象被MFC(如CWnd和CMenu)包装作为选择。
MFC设计者所用的使类库带来的总开销减到最小的方法之一是在MFC对象与Windows对象之间的关系中得到了体现。在Windows中,有关窗口特性和目前状态的信息被保存在操作系统拥有的内存中。这些信息对应用程序是隐藏的,应用程序只能处理窗口句柄或HWND。MFC并没有复制在CWnd类的数据成员中的与HWND有关的所有信息,事实上,MFC通过将HWND存储在称为m_hWnd的公用CWnd数据成员中,而在CWnd内包装了一个窗口。作为规则,如果Windows通过某些类型的句柄展示一个对象,那么相应的MFC类就会包含那个句柄的数据成员。如果我们想要调用API函数,该函数要求一个句柄,但是我们只有CWnd或CWnd指针,而不是HWND,那么这些知识对于我们来说是有用的。
3.1.3 MFC类的分层结构
MFC提供了许多设计好的类来满足广泛的需要。大多数MFC类都是从CObject中直接或间接地派生出来的。CObject给那些继承它的类提供了3个重要的特性:
― 串行化支持
― 运行时类信息支持
― 诊断和调试支持
串行化是对象的永久数据流出或流入存储介质(如磁盘文件)的进程。把CObject作为基类,可以创建可串行化的类,其实例容易存储和重新创建。运行的类信息(RTCI)允许我们在运行时检索对象的类名称及对象的其他信息。RTCI的执行不同于C++中的运行时类型信息(RTTI)机制,因为它比RTTI要早出现好多年。嵌入在CObject中的诊断和调试支持允许我们对CObject派生类的实例执行有效性检查,并将状态信息转储到一个调试窗口。
CObject对它的派生类还提供了别的好处。例如,重载new和delete运算符防止内存泄漏。如果从CObject派生类创建了一个对象,而没有在应用程序结束前删除它,那么MFC将会在调试输出窗口写一条警告信息。随着我们对MFC越来越熟悉,这个MFC类基本特点的重要性也会变得越来越清晰。
3.1.4 MFC程序结构分析
MFC作为微软应用程序的框架,举足轻重。MFC是C++的Win32 API。更重要的是,它提供了文档/视图框架;sdk编程中的 wndproc也变成了方便的消息映射;令人恐惧的设计精妙的宏。
1.Windows程序工作原理
Windows程序设计是一种完全不同于传统的DOS方式的程序设计方法,它是一种事件驱动方式的程序设计模式。在程序提供给用户的界面中有许多可操作的可视对象。用户从所有可能的操作中任意选择,被选择的操作会产生某些特定的事件,这些事件发生后的结果是向程序中的某些对象发出消息,然后这些对象调用相应的消息处理函数来完成特定的操作。Windows应用程序的最大特点就是程序没有固定的流程,而只是针对某个事件的处理有特定的子流程,Windows应用程序就是由许多这样的子流程构成的。
从上面的讨论中可以看出,Windows应用程序在本质上是面向对象的。程序提供给用户界面的可视对象在程序的内部一般也是一个对象,用户对可视对象的操作通过事件驱动模式触发相应对象的可用方法。程序的运行过程就是用户的外部操作不断产生事件,这些事件又被相应的对象处理的过程。
2.建立应用程序
在“Build”菜单中选择“Rebuild All”菜单项,系统开始编译由AppWizard自动生成的程序框架中所有文件中的源代码,并且链接生成可执行的应用程序。按“Ctrl+F5”,应用程序就开始运行了,虽然我们没有编写一行代码,但是可以看出由系统自动生成的应用程序的界面已经有了一个标准Windows应用程序所需的几个组成部分,我们要做的事情是往这个应用程序中添加必要的代码以完成我们所需要的功能。
接下来将要来介绍Windows下自动生成的这个应用程序框架,让我们对MFC方式的Windows应用程序的工作原理有全面的认识,只有这样才知道应该如何往程序框架当中添加需要的代码。
3.程序结构剖析
使用MFC方式的应用程序的4个主要类之间的关系。
CMYAPP类主要的作用是用来处理消息的,它统一管理程序收到的所有的消息,然后把消息分配到相应的对象。CMAINFRAME是CMYVIEW的父类,也就是说视窗VIEW显示在主框窗MAINFRAME的客户区中。类CMYVIEW的作用是显示数据,而数据的来源是类CMYDOC,在MFC程序中,程序的数据是放在文档当中的,而显示数据则是利用视窗方式,文档与视窗分离带来的好处就是一个文档可以同时具有多个视窗,每个视窗只显示文档中的一部分数据,或者以特定的风格显示文档中的数据。文档与视窗分离的另一个好处就是在程序中可以处理多个文档,通过对不同的视窗的处理达到对不同的文档分别处理的目的。
使用过传统的Windows编程方法的人都知道,在应用程序中有一个重要的函数WinMain(),这个函数是应用程序的基础,用户操作所产生的消息正是经过这个函数的处理派送到对应的对象中进行处理的。在MFC方式的Windows应用程序中,用来处理消息的是系统自动生成的MFC中的类CWINAPP的派生类CMYAPP,下面就从这个类开始介绍应用程序的框架。
(1)类CMYAPP
类CMYAPP是应用程序运行的基础,这个类是由MFC中的类CWINAPP派生来的。在这个类中除了有一般类都有的构造函数,一个重要的成员函数就是INITINSTANCE,我们知道,在Windows环境下面可以运行同一程序的多个实例,函数INITINSTANCE的作用就是在生成一个新的实例的时候,完成一些初始化的工作。
下面研究INITINSTANCE函数所做的事情,我们开始定义了一个文档模板对象指针PDOCTEMPLATE,通过NEW操作符,系统动态生成了这个文档模板对象,然后使用ADDDOCTEMPLATE函数把这个文档模板对象加入到应用程序所维护的文档模板链表当中,这个文档模板PDOCTEMPLATE的作用就是把程序用到的框架窗口,CMAINFRAME,文档CMYDOC,视窗CMYVIEW与应用对象CMYAPP联系起来。
CMYAPP类提供了用户与Windows应用程序之间进行交流的界面。在生成这个类的对象后,这个对象自动地把自身与Windows系统建立联系,接收Windows传送的消息,并交给程序中相应的对象去处理,这就免去了程序员许多的工作,使得开发C++的Windows程序变得简单方便。
(2)类CMAINFRAME
类CMAINFRAME是由MFC中的CFRAMEWND派生来的,所以它也是一个框架窗口。前面已经指出,CMAINFRAME是类CMYVIEW的父类,也就是说CMYVIEW类的对象显示在主框架窗口的客户区中。在类CMAINFRAME中,系统已经从类CFRAMEWND那里继承了处理窗口的一般事件的Windows消息,比如改变窗口的大小,窗口最小化等的成员函数,因此编程的时候程序员不需要再关心此类消息的处理,从而减轻了程序员的负担。当然,如果确实需要重新编写处理此类消息的成员函数,则需要对原有的成员函数进行重载。
在MFC程序中,我们并不需要经常对CMAINFRAME类进行操作,更多的是对视窗类进行操作,达到对程序中的数据进行编辑和修改的目的。
最后要指出的是,在MFC方式的程序中,当程序的一个实例被运行的时候,系统根据前面在CMYAPP类中介绍的文档模板对象自动生成类CMAINFRAME,CMYVIEW,CMYDOC的对象,而不需要程序员主动地去创建这些类的对象。
(3)类CMYVIEW与CMYDOC
之所以把CMYVIEW类和CMYDOC类放一起来介绍,是因为这两个类是密切相关的,文档是由文档模板对象生成的,并由应用程序对象管理,而用户则是通过与文档相联系的视窗对象来存储、管理应用程序的数据,用户与文档之间的交互则是通过与文档相关联的视窗对象来进行的。
生成一个新的文档的时候,MFC程序同时生成一个框架窗口,并且在框架窗口的客户区中生成一个视窗对象作为框架窗口的子窗口,这个子窗口以可视化的方式表现文档中的内容。视窗的重要功能就是负责处理用户的鼠标、键盘等操作,通过对视窗对象的处理达到处理文档对象的目的。
要指出的一点是,Windows应用程序分单文档界面SDI和多文档界面MDI两种,在单文档界面中,文档窗口与主框架窗口是同一概念。而这时的视窗对象则是显示在文档窗口的客户区当中。前面生成的test程序使用的就是单文档界面方式,此时文档窗口是主框架窗口,即类CMAINFRAME的对象。
3.1.5 MFC中的WinMain函数
WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束。WinMain函数的原型如下:
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的。第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数。第二个参数hPrevInstance表示当前实例的前一个实例的句柄。通过查看MSDN我们可以知道,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。
3.1.6 MFC库消息映射
MFC库应用框架并没有采用虚函数来处理Windows消息,相反,它通过一些宏来将特定的消息映射到派生类中相应的成员函数。库类应用框架没有采用虚函数,这主要是因为以下原因:库类中包含了5个窗口类,分别和Windows的5种窗口类型相对应,如果采用虚函数的办法来处理消息,那么每个窗口基类就要对140条消息分别定义一个虚函数。C++对每个虚函数都要求有一个4字节的传递结构,称做“vtable”,无论该虚函数是否在派生类中被重新定义,“vtable”都必不可少。这样,对于每个特定类型的窗口或控件,应用都需要一个2.8KB大小的表来支持虚消息控制函数。
那么对于菜单命令消息及按钮命令消息来说,设计消息控制函数不能将它们定义成窗口基类中的虚函数,因为不同的应用会有不同的菜单项和按钮。MFC库的这种消息映射系统就避免了使用大的vtable表,并且能够处理各种各样应用的命令消息。这种体制也允许某些非窗口类(如文档类和应用类)来控制命令消息。这种消息映射体制同Borland作为OWL的一个组成部分所提供的“动态传递表”系统也有所不同,它不需要C++作任何扩展。
MFC库消息控制函数要求我们提供函数原型、函数体,以及在消息映射中的入口,而ClassWizard会帮助我们将消息控制函数引入我们所设计的类中,只要我们从列表框中选择一个Windows消息ID,ClassWizard就会自动产生具有正确参数及返回值的代码。