MFC对象的创建
前面几章介绍了MFC的核心概念和思
想,即介绍了MFC对Windows对象的
封装方法和特点;MFC对象的动态创
建、序列化;MFC消息映射机制。
现在,考查MFC的应用程序结构体系,
即以文档-视为核心的编程模式。学
习本章,应该弄清楚以下问题:
MFC中诸多MFC对象的关系:应用程序对象,文档对象,边框窗口对象,文档边框窗口对象,视对象,文档模板对象等。
MFC对象的创建和销毁:由什么对象创建或销毁什么对象,何时创建,何时销毁?MFC提供了那些接口来支持其编程模式?
1.MFC对象的关系
1.创建关系
这里讨论应用程序、文档模板、边框窗口、视、文档等的创建关系。
图5-1大略地表示了创建顺序,但表5-1更直接地显示了创建与被
创建的关系。
表5-1 MFC对象的创建关系
2.交互作用关系
应用程序对象有一个文档模板列表,存放一个或多个文档模板对象;文档
模板对象有一个打开文档列表,存放一个或多个已经打开的文档对象;文
档对象有一个视列表,存放显示该文档
数据的一个或多个视对象;还有一个指
针指向创建该文档的文档模板对象;视
有一个指向其关联文档的指针,视是一
个子窗口,其父窗口是边框窗口(或者
文档边框窗口);文档边框窗口有一个
指向其当前活动视的指针;文档边框窗
口是边框窗口的子窗口。
Windows 管理所有已经打开的窗口,把
消息或事件发送给目标窗口。通常,命令消息发送给主边框窗口。
图5-2大略地表示了上述关系:
MFC 提供了一些函数来维护这些关系。
表5-2列出了从一个对象得到相关对象的方法。
表5-2 从一个对象得到另一个对象的方法
表5-3 从一个对象通知另一个对象的方法:
可以通过表5-2得到相关对象,再调用表5-3中相应的函数。例如:视在接受了新数据或者数据被修改之后,使用表5-2中的函数GetDocument 得到关联文档对象,然后调用表5-3中的文档函数UpdateAllViews更新其他和文档对象关联的视。
在表5-2和表5-3中,CView对象指CView或派生类的实例;成员函数列中如果没有指定类属,就是第一列对象的类的成员函数。
2.MFC提供的接口
MFC编程就是把一些应用程序特有的东西填入MFC框架。MFC提供了两种填入的方法:一种就是使用前一章论述的消息映射,消息映射给应用程序的各种对象处理各种消息的机会;另一种就是使用虚拟函数,MFC在实现许多功能或者处理消息、事件的过程中,调用了虚拟函数来完成一些任务,这样就给了派生类覆盖这些虚拟函数实现特定处理的机会。
下面两节将列出两类接口,有两个目的:一是为了让读者获得整体印象,二是后文将涉及到或者讨论其中的许多函数时,不显得突兀。
1.虚拟函数接口
几乎每一个MFC类都定义和使用了虚拟成员函数,程序员可以在派
生类中覆盖它们。一般,MFC提供了这些函数的缺省实现,所以覆
盖函数应该调用基类的实现。这里给出一个MFC常用虚拟函数的总
览表(见表5-4),更详细的信息或它们的缺省实现动作参见MFC文
档。由于基类的虚拟函数被派生类继承,所以在派生类中不作重复
说明。
覆盖基类的虚拟函数可以通过ClassWizard进行,不过,并非所有
的函数都可以这样,有的必须手工加入函数声明和实现。
表5-4 常见MFC类的虚拟函数接口
2.消息映射方法和标准命令消息
窗口对象可以响应以“WM_”为前缀的标准Windows消息,消息处理函数名称以“ON”为前缀。不同类型的Windows窗口处理的Windows消息是有所不同的,因此,不同类型的MFC窗口实现的消息处理函数也有所不同。例如,多文档边框窗口能处理WM_MDIACTIVATE消息,其他类型窗口就不能。程序员从一定的MFC窗口派生自己的窗口类,对感兴趣的消息,覆盖基类的消息处理函数,实现自己的消息处理函数。
所有的命令目标(CCmdTarger或导出类对象)可以响应命令消息,程序员可以指定应用程序对象、框架窗口对象、视对象或文档对象等来处理某条命令消息。一般地,尽量由与命令消息关系密切的对象来处理,例如隐藏/显示工具栏由框架窗口处理,打开文件由应用程序对象处理,数据变化的操作由文档对象处理。
对话框的控制子窗口可以响应各类通知消息。
对于命令消息,MFC实现了一系列标准命令消息处理函数。标准命令ID 在afxres.h中定义。表5-5列出了MFC标准命令的实现,从ID或者函数
名可以大致地看出该函数的目的、功用,具体的实现有的后续章节会讲解,详细参见MFC技术文档。
程序员可以自己来处理这些标准消息,也可以通过不同的类或从不同的类导出自己的类来处理这些消息,不过最好遵循MFC的缺省实现。比如处理ID_FILE_NEW命令,最好由CWinApp的派生类处理。
表5-5 标准命令消息处理函数
3.MFC对象的创建过程
应用程序使用MFC的接口是把一些自己的特殊处理填入MFC框架,这些处理或者在应用程序启动和初始化的时候被调用,或者在程序启动之后和用户交互的过程中被调用,或者在程序退出和作清理工作的时候被调用。这三个阶段中,和用户交互阶段是各个程序自己的事情,自然都不一样,但是程序的启动和退出两个阶段是MFC框架所实现的,是MFC框架的一部分,各个程序都遵循同样的步骤和规则。显然,清楚MFC框架对这两个阶段的处理是很有必要的,它可以帮助深入理解MFC框架,更好地使用MFC框架,更有效地实现应用程序特定的处理。
MFC程序启动和初始化过程就是创建MFC对象和Windows对象、建立各种对象之间的关系、把窗口显示在屏幕上的过程,退出过程就是关闭窗口、
销毁所创建的Windows对象和MFC对象的过程。所以,下面要讨论几种常用MFC对象的结构,它们是构成一个文档-视模式应用程序的重要部件。
1.应用程序中典型对象的结构
本节将主要分析应用程序对象、文档对象、文档模板等的数据结构。
通过考察类的结构,特别是成员变量结构,弄清它的功能、目的以
及和其他类的关系;另外,在后续有关分析中必定会提到这些成员
变量,这里先作个说明,到时也不会显得突兀。
下面几节以表格的形式来描述各个类的成员变量。表格中,第一列
打钩的表示是MFC类库文档有说明的;没打钩的在文档中没有说
明,如果是public,则可以直接访问,但随着MFC版本的变化,
以后MFC可能不支持这些成员;第二列是访问属性;第三列是成员
变量名称;第四列是成员变量的数据类型;第五列是对成员变量的
功能、用途的简要描述。
1.应用程序类的成员变量
应用程序对象的数据成员表由两部分组成,第一部分是
CWinThread的成员变量,如表5-6所示,CWinApp继承了
CWinThread的数据成员。第二部分是CWinApp自己定义的
成员变量,如表5-7所示。
表5-6 CwinThread的成员变量
表5-7 CWinApp的成员变量
2.CDocument的成员变量
表5-8 文档对象的属性。
3.文档模板的属性
表5-9列出了文档模板的成员变量,5-10列出了单文档模板的成员变量,5-11列出了多文档模板的成员变量。单、多文档模板继承了文档模板的成员变量。
表5-9 文档模板的数据成员
表5-10 单文档模板的成员变量
表5-11 单文档模板的成员变量
2.WinMain入口函数
1.WinMain流程
现在讨论MFC应用程序如何启动。
WinMain函数是MFC提供的应用程序入口。进入WinMain前,
全局应用程序对象已经生成。WinMain流程如图5-3所示。
图中,灰色框是对被调用的虚拟函数的注释,程序员可以或
必须覆盖它以实现MFC要求的或用户希望的功能;大括号所
包含的图示是相应函数流程的细化,有应用程序对象App
的初始化、Run函数的实现、PumpMessage的流程,等等。
从图中可以看出:
(1)一些虚拟函数被调用的时机
对应用程序类(线程类)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage 来说,InitInstance在应用程序初始化时调用,ExitInstance在程序退出时调用,Run在程序初始化之后调用导致程序进入消息循环,ProcessMessageFilter、OnIdle、
PreTranslateMessage在消息循环时被调用,分别用来过滤
消息、进行Idle处理、让窗口预处理消息。
(2)应用程序对象的角色
首先,应用程序对象的成员函数InitInstance被WinMain
调用。对程序员来说,它就是程序的入口点(真正的入口点
是WinMain,但MFC向程序员隐藏了WinMain的存在)。由
于MFC没有提供InitInstance的缺省实现,用户必须自己
实现它。稍后将讨论该函数的实现。
其次,通过应用程序对象的Run函数,程序进入消息循环。
实际上,消息循环的实现是通过CWinThread::Run来实现
的,图中所示的是CWinThread::Run的实现,因为CWinApp
没有覆盖Run的实现,程序员的应用程序类一般也不用覆盖
该函数。
(3)Run所实现的消息循环
它调用PumpMessage来实现消息循环,如果没消息,则进行
空闲(Idle)处理。如果是WM_QUIT消息,则调用
ExitInstance后退出消息循环。
(4)CWinThread::PumpMessage
该函数在MFC函数文档里没有描述,但是MFC建议用户使用。
它实现获取消息,转换(Translate)消息,发送消息的消息
循环。在转换消息之前,调用虚拟函数
PreTranslateMessage对消息进行预处理,该函数得到消息
目的窗口对象之后,使用CWnd的WalkPreTranslateTree
让目的窗口及其所有父窗口得到一个预处理当前消息的机
会。关于消息预处理,见消息映射的有关章节。如果是
WM_QUIT消息,PumpMessage返回FALSE;否则返回TRUE。
2.MFC空闲处理
MFC实现了一个Idle处理机制,就是在没有消息可以处理时,进行Idle处理。Idle处理的一个应用是更新用户接口对象的状态。更新用户接口状态的内容见消息映射的章节。
1.空闲处理由函数OnIdle完成,其原型为BOOL OnIdle(int)。参数的含
义是当前空闲处理周期已经完成了多少次OnIdle调用,每个空闲处理周
期的第一次调用,该参数设为0,每调用一次加1;返回值表示当前空闲
处理周期是否继续调用OnIdle。
2.MFC的缺省实现里,CWinThread::OnIdle完成了工具栏等的状态更新。如
果覆盖OnIdle,需要调用基类的实现。
3.在处理完一个消息或进入消息循环时,如果消息队列中没有消息要处理,
则MFC开始一个新的空闲处理周期;
4.当OnIdle返回FASLE,或者消息队列中有消息要处理时,当前的空闲处
理周期结束。
从图5-3中Run的流程上可以清楚的看到MFC空闲处理的情况。
本节描述了应用程序从InitInstance开始初始化、从Run进入消息循环的过程,下面将就SDI应用程序的例子描述该过程中创建各个所需MFC对象的流程。
1.SDI应用程序的对象创建
如前一节所述,程序从InitInstance开始。在SDI应用程
序的InitInstance里,至少有以下语句:
//第一部分,创建文档模板对象并把它添加到应用程序的模
板链表
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTView));
AddDocTemplate(pDocTemplate);
//第二部分,动态创建文档、视、边框窗口等MFC对象和对
应的Windows对象
//Parse command line for standard shell commands, DDE,
file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
C++虚函数与纯虚函数用法与区别 1.C++虚函数与纯虚函数用法与区别,.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。 2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class) 只有声明而没有定义。 3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。 4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。 5.虚函数的定义形式:virtual {method body} 纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。 6. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。 //father class class Virtualbase { public: virtual void Demon()= 0; //prue virtual function virtual void Base() {cout<<"this is farther class"<}; }
虚函数和纯虚函数 在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。 那么,什么是虚函数呢,我们先来看看微软的解释: 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。 ——摘自MSDN 这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子: #include "stdio.h" #include "conio.h" class Parent { public: char data[20]; void Function1(); virtual void Function2(); // 这里声明Function2是虚函数 }parent; void Parent::Function1() { printf("This is parent,function1\n"); } void Parent::Function2() { printf("This is parent,function2\n"); } class Child:public Parent { void Function1(); void Function2(); } child; void Child::Function1() { printf("This is child,function1\n");
C++中虚析构函数的作用 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明: 有下面的两个类: class ClxBase { public: ClxBase() {}; virtual ~ClxBase() {cout<<”aaa”<
sizeof计算含有虚函数的类的空间大小 当我们计算一种数据类型所占用的空间大小时,很easy,sizeof就可以解决掉。如果我们计算一个类,一个空类,或者一个含有虚函数然后又派生子类时,这时候他们所占用的内存空间是如何变化的呢?下面我们就通过代码来介绍下。 一个不含有虚函数的普通类与其派生类的内存关系 class Base { public: Base(int x):a(x) {} void print() { cout<