再上一篇:14.3 标准设备的输入/输出
上一篇:14.4 文件流
主页
下一篇:15.2 文档与视图结构
再下一篇:15.3 MFC的数组类
文章列表

第十五章 MFC程序设计基础

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

本章简要介绍MFC程序设计基础知识,利用MFC提供的工具设计应用程序界面的方法,使用 VC++提供的编程向导(AppWizard)生成应用程序的基本框架,并用类向导(ClassWizard)建立应用程序的消息处理机制。由于MFC比较复杂,本章结合几个实例,说明应用程序边框、文档边框和视图之间的关系,菜单设计的方法,键盘输入事件和鼠标事件的处理方法。

15.1 WINDOWS和MFC编程

VC++编程方法可分为二种:一种是非WINDOWS编程,另一种是WINDOWS编程。前面章节介绍的均为非WINDOWS编程。WINDOWS编程方法又可分为二种:一种是直接调用WINDOWS提供的WIN32 API(应用程序接口)函数开发WINDOWS应用程序;另一种是使用VC++提供的MFC(Microsoft Foundation Class-基础类),它为开发应用程序者提供了大量的类和代码支持,使用集成环境中的编程向导可以很容易地生成应用程序的基本框架,并用类向导建立应用程序的消息处理机制,在此基础上设计出满足应用需求的完整的应用程序。

15.1.1 MFC类的层次结构

1987年微软公司推出WINDOWS,为应用程序设计者提供了WIN16 API,在此基础上推出了WINDOWS GUI(图形用户界面),然后采用面向对象技术对接口进行包装,1992年推出应用程序框架产品AFX(Application Frameworks)。在AFX的基础上进一步发展为MFC产品。

MFC类的层次结构如图15-1所示。

CObject类是MFC提供的绝大多数类的基类。该类完成动态空间的分配与回收,支持一般的诊断,出错信息处理和文档串行化等。在头文件afx.h中给出了CObject类的定义。为了便于了解有关MFC类的定义,对两种函数的原型说明作简单介绍:

void* PASCAL operator new(size_t nSize);

BOOL IsSerializable() const;

在函数名前的PASCAL表示函数的参数按PASCAL语言规定的格式入栈,这种格式可提高传递函数参数的效率。BOOL是整数类型,表示函数的返回值只能为1(表示逻辑真)或0(表示逻辑假)。

CCmdTarget类主要负责将系统事件和窗口事件发送给响应这些事件的对象,完成消息发送、等待光标、派遣(调度)等工作,实现应用程序的对象之间协调运行。

CWinThread类为线程类,它完成对线程的控制,包括产生线程,终止线程,运行线程,挂起线程等。

CObject

CCmdTarget

CWinThread CWnd CDocument

图15-1 MFC类的层次结构

CWinApp类是应用程序的主线程类,它由CWinThread类派生。任何一个MFC应用程序只能有一个CWinApp类的对象。

CWnd类是窗口类,该类及其派生类的对象均为窗口。每一个窗口都能接收和处理窗口事件。

CFrameWnd类是一个边框窗口类,它包含标题栏、系统菜单、边框、最小/最大化按钮和一个视图窗口。

CMDIFrameWnd类是多文档边框窗口类,它的对象是多文档界面图文框窗口。

CMDIChildWnd 类是多文档子窗口类,应用程序产生多个文档窗口时,每一个文档窗口均为该类的一个对象。

CMinFrameWnd类是一种简化的较单一的文本框式的图文框窗口类。

CView类是一个视图类,它为应用程序的用户与WINDOWS之间提供了一个输入/输出的接口。主要负责接收来自键盘或鼠标的输入,允许用户对数据的观察或打印。一个视图类对象总是与文档对象相关联,一个文档类可关联多个视图对象(对同一个文档作不同的视图处理),而一个视图对象只能与一个文档对象相关联。

CDialog类为对话框类。该类主要用作输入/输出的一种界面。

CDocument类为文档类。要对文档进行处理时,必须创建文档类或该类的派生类对象。文档包含了应用程序在运行期间所用到的数据。视图类是数据的图示形式。只有通过文档类的对象才能对数据进行修改或处理。

15.1.2 VC++集成环境产生的项目类型

在VC++集成环境下,选择“File”菜单中的“New”命令,即会弹出一个如图15-2所示的对话框。该对话框列出了VC++集成环境可产生所有不同类型的项目。

图15-2 项目标签对话框

每一种类型的项目含义为:ATL COM AppWizard 为ATL(活动模板库)应用程序项目。Cluster Resource Type Wizard为收集资源类型项目。Custom AppWizard为用户自定义应用程序项目。Database Project 为数据库项目。DevStudio Add-in Wizard为自动化宏项目。ISAPIExtension Wizard 为Internet服务器扩展项目。Makefile为Makefile项目。MFC ActiveX ControlWizard为ActiveX控件程序项目。MFC AppWizard(.dll)为MFC动态连接库项目。MFCAppWizard .exe)为MFC可执行程序项目。Utility Project为实用程序项目。Win32 Application为Win32应用程序项目,使用Win32 API进行程序设计的项目。Win32 Console Application 为非窗口界面的控制台应用程序项目。最后两个为产生动态/静态连接库项目。

15.1.3 应用程序向导AppWizard

使用应用程序向导可生成应用程序的框架,并生成与类向导相兼容的应用程序源文件、资源文件和头文件等。生成应用程序框架的步骤为:

(1)进入VC++集成环境,选择File菜单中的New命令,产生如图15-2所示的对话框。

(2)若项目标签Project不起作用,则用鼠标左键单击项目标签Project。为叙述简单起见,本章中凡是讲“单击”,均是指“用鼠标左键单击”。单击MFC AppWizard(. exe),表示要产生一个MFC应用程序项目,见图15-2。指定项目文件名所在的目录(文件夹)位置,并在项目文件名(Project name)编辑框中输入项目文件名。设输入的项目文件名为test1。选中“Create new workspace”复选按钮,指明要产生一个新的工作区。目标平台(Plateforms)取缺省值WIN32。若单击“ok”按钮,则产生如图15-3所示的选择应用程序结构的对话框。

图 15-3 选择应用程序结构的对话框

(3)该对话框用来指定产生应用程序的类型,确定是否要文档/视图支持,选择使用哪一国家的语言。有三种应用程序的类型可供选择,分别为单文档(Signle document)应用程序,多文档(Multiple document)应用程序和基于对话框的应用程序。选择多文档,其它取缺省值。单击“Next”按钮。弹出图15-4所示的对话框,其窗口的标题为“MFC AppWizard –Step 2 of 6”。

(4)该对话框用来指定应用程序所需要的数据库支持。其中None表示不支持ODBC库;Header files only表示仅包含数据库的头文件,但不产生与数据库相关的类;Database viewwithout file support表示要包含数据库的头文件,并创建数据库视图(记录视图),但不支持数据库文件;Database view with file support表示包含数据库的头文件,并创建数据库视图(记录视图),支持数据库文件。选择None,并单击“Next”,则产生图15-5对话框,其窗口的标题为“MFC AppWizard – Step 3 of 6”。

(5)该对话框用来指定复合文档支持,是否包含ActiveX控件等。其中None表示不支持链接和嵌入的对象;Container表示应用程序包含被链接和嵌入的对象;Mini-server表示应用程序只支持嵌入的对象;Full-server表示应用程序提供全部服务,应用程序可独立运行,对象可包含在复合文档中。选择“None”,其它取缺省值,单击“Next”按钮,产生一个窗口标题为“MFC AppWizard – Step 4 of 6”的对话框,自己上机操作,对话框图略。

图15-4 选择数据库支持

(6)该对话框用来指定应用程序的界面特性,工具条的格式,及在文件列表中要保存的文件个数。取缺省值,单击“Next” 按钮,产生一个窗口标题为“MFC AppWizard – Step5 of 6”的对话框。

(7)该对话框用来指定项目的风格,源程序中是否包含注解和连接方式。项目的风格可以是MFC风格或WINDOWS浏览器风格。通常希望在源程序中产生注解,以便根据注解加入相应的程序代码。可指定使用静态或动态连接库,当应用程序中既有MFC程序代码,又有非MFC代码代码时,使用静态连接;否则使用动态连接。取缺省值,单击“Next” 按钮,产生一个窗口标题为“MFC AppWizard – Step 6 of 6”的对话框。

(8)该对话框列出了编程向导所产生的类名,头文件及源程序文件。单击“Next” 按钮,产生一个窗口标题为“New Project Information”的对话框。

(9)该对话框列出了编程向导根据用户指定的需求所产生的头文件和源程序文件的概要说明。单击“Ok” 按钮,编程向导则根据用户的要求产生相应的应用程序源文件。在以上的每一步中,若选择“Back” 按钮,则返回前一步,即返回到前一个对话框;若选择“Cancel” 按钮,则取消前面所做的工作。若选择“Fnish” 按钮,则按缺省的方式产生应用程序的框架。

图15-5 复合文档支持对话框

经过以上步骤后,编程向导已产生一个完整的应用程序框架。对这个工程项目文件进行编译和连接,产生一个可执行程序。运行该应用程序时,在显示器上产生一个如图15-6所示的应用程序窗口。该窗口包含了标题栏、菜单条、工具条和状态栏。菜单条中的部分菜单已提供了实现菜单功能的程序代码,部分菜单没有提供实现代码。

15.1.4 应用程序类和源文件的结构

应用程序向导为工程文件test1生成了六个类,分别是:

(1)文档类CTest1Doc,它由类CDocument派生出来,在文件CTest1Doc.h中给出该类的定义,该类的成员函数的实现部分(成员函数的框架)在CTest1Doc.cpp中给出。该类的数据成员通常为应用程序所用到的数据,包括准备存入数据文件中的数据或从文件中取出数据,成员函数主要完成对该类中的数据进行加工处理。为此要根据实际应用程序的需要,在该类中增加相应的数据成员和成员函数。

(2)视图类CTest1View,它由类CView派生出来,在文件CTest1View.h中定义了该类,在CTest1View.cpp中给出该类的成员函数的实现框架。视图类根据应用程序的需求,完成文档中数据的显示,以及接收用户输入并解释用户输入。

图15-6 应用程序窗口

(3)应用程序窗口类 CMainFrame,它由类 CMDIFrameWnd派生出来,在文件MainFrm .h中定义了该类,在MainFrm .cpp中给出该类的成员函数的实现框架。该类用于管理应用程序窗口,显示标题栏、工具条、状态栏、控制菜单。该窗口是所有多文档子窗口的容器。

(4)多文档子窗口类 CChildFrame,它由类 CMDIChildWnd 派生出来,在文件ChildFrm .h中定义了该类,在ChildFrm .cpp中给出该类的成员函数的实现框架。该类管理打开的文档。每一个文档类及其对应的视图类都有一个单独的MDI子窗口。

(5)应用程序类CTest1App,它由类CWinApp派生出来,在文件test1.h中定义了该类,在test1.cpp中给出该类的成员函数的实现框架。该类用于控制应用程序的所有对象(文档、视图、文档子窗口),并完成应用程序的初始化工作,以及程序结束时的收尾工作。test1.h是一个主要的头文件,test1.cpp是一个主要的应用程序文件。

用MFC向导产生的每一个应用程序必定有一个CWinApp的实例对象theApp,由它来创建和管理文档、视图、应用程序窗口等。

(6)对话框类 CAboutDlg,它由类 CDialog派生出来,类的定义和实现都在文件test1.cpp中给出。它用于产生和管理应用程序版本的对话框。

应用程序向导除了生成以上六个类所对应的五个.cpp文件和五个.h文件外,还生成以下程序文件:

(1) Resource.h,定义应用程序所用到的所有资源符号(宏)。

(2) StdAfx.h 和 StdAfx.cpp,用于生成预编译的头文件 Test1.pch和预编译类型文件StdAfx.obj。StdAfx.h包含了系统头文件和本项目常用的但很少改动的头文件。StdAfx.cpp通常仅包含头文件StdAfx.h 。

(3) Test1.clw,是应用程序向导生成的数据库文件。

(4) Test1.rc,是一个包含资源描述信息的资源文件,可用Developer Studio的资源编辑器直接编辑该文件中的资源。

(5) Res\Test1.rc2,包含Developer Studio资源编辑器不能直接编辑的资源。

(6) Res\test1Doc.ico,是应用程序的MDI窗口的图标文件。

(7) Res\Toolbar.bmp,是应用程序窗口中的工具栏的位图文件。

(8) Res\test1.ico,是应用程序的图标文件。

(9) ReadMe.txt,该文件简单地说明由应用程序向导所生成的各个文件的作用与用途。

(10) Test1.dsp,是一个项目文件,它包含了项目级的信息。

在编辑或阅读程序文件时,要注意以下几点:

(1)凡是注解为"TODO:" 的地方,均为可加入相应的应用程序代码的开始位置。

(2)如下形式部分不能修改:

//{{AFX_MSG_MAP(CTestm1App)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

// NOTE - the ClassWizard will add and remove mapping macros here.

// DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG_MAP

即以//{{AFX_…开头的行,到以//}}AFX_..结束的行之间的部分,不能改动。其内容不是注解行,而是由MFC编译预处理程序要作特殊处理的部分。这部分内容是由类向导根据应用程序的生成过程自动增加或删除相应的宏。

(3) 以下形式是编译预处理的宏,不能修改:

BEGIN_MESSAGE……

……

END_MESSAGE……

用BEGIN_…和END_…括起来的部分是消息变换(映射)的宏,其内容不能修改,它由类向导根据应用程序的需求增加或删除相应的宏。

15.1.5 应用程序运行过程分析

应用程序的初始化、运行和结束工作都是由应用程序类的实例(对象)来控制完成的。对于非WINDOWS应用程序,均有一个main函数;而对于WINDOWS应用程序,均有一个WinMain函数。因对任一个MFC应用程序,WinMain函数要完成的工作均是相同的。因此,该函数不要程序设计者编写,而由MFC类库自动提供。

用编程向导产生的应用程序的执行过程为:首先调用构造函数,创建全局应用程序对象theApp;进入WinMain函数,调用theApp的二个成员函数InitApplication和InitInstance,完成初始化工作;调用成员函数Run,执行应用程序的消息循环,即重复执行接收消息并转发消息的工作;结束程序时,调用成员函数 ExitInstance,完成终止应用程序的收尾工作。其它工作均有事件(消息)来驱动的。

15.1.5.1 成员函数InitInstance

对任一MFC应用程序,InitInstance函数的结构框架是相同的,完成的主要工作也是相同的。工程项目Test1中的InitInstance函数的程序代码为:

BOOL CTest1App::InitInstance( )

{

AfxEnableControlContainer();

// Standard initialization

// If you are not using these features and wish to reduce the size

// of your final executable, you should remove from the following// the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls(); // Call this when using MFC in a shared DLL#else

Enable3dControlsStatic(); // Call this when linking to MFC statically#endif

// Change the registry key under which our settings are stored.

// TODO: You should modify this string to be something appropriate// such as the name of your company or organization.

SetRegistryKey(_T("Local AppWizard-Generated Applications"));LoadStdProfileSettings(); // Load standard INI file options (including MRU)// Register the application's document templates. Document templates// serve as the connection between documents, frame windows and views.CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_TESTM1TYPE,

RUNTIME_CLASS(CTestm1Doc),

RUNTIME_CLASS(CChildFrame), // custom MDI child frame

RUNTIME_CLASS(CTestm1View));

AddDocTemplate(pDocTemplate);

// create main MDI Frame window

CMainFrame* pMainFrame = new CMainFrame;

if (!pMainFrame->LoadFrame(IDR_MAINFRAME))

return FALSE;

m_pMainWnd = pMainFrame;

// Parse command line for standard shell commands, DDE, file openCCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

if (!ProcessShellCommand(cmdInfo))

return FALSE;

// The main window has been initialized, so show and update it.

pMainFrame->ShowWindow(m_nCmdShow);

pMainFrame->UpdateWindow();

return TRUE;

}

InitInstance函数主要完成以下五个方面的任务:

(1)确定以何种方式使能3D控制

确定以动态方式或静态方式调用MFC类库。这部分的代码为:

#ifdef _AFXDLL

Enable3dControls(); // Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic(); // Call this when linking to MFC statically

#endif

(2) 完成应用程序的注册并装入标准INI文件选项,程序代码为:

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();

使用全局函数SetRegistryKey和宏_T更改注册键,保存应用程序的设置。并从INI文件中装入标准的文件选项和WINDOWS注册信息,包括注册表的Most Recently Used列表框。

(3) 创建并注册文档模板,程序代码为:

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_TESTM1TYPE, //资源号

RUNTIME_CLASS(CTestm1Doc), //文档窗口

RUNTIME_CLASS(CChildFrame), //custom MDI child frame,边框窗口

RUNTIME_CLASS(CTestm1View)); //视图窗口

AddDocTemplate(pDocTemplate);

文档模板提供了将MFC应用程序的文档、视图和边框窗口结合在一起的框架结构,通过创建文档模板后,指针pDocTemplate将把应用程序的文档、视图和边框窗口对象连接在一起。其中宏RUNTIME_CLASS的每一次调用都返回指定类的信息(指向CRuntimeClass结构的指针)。

每当创建模板后,必须用成员函数AddDocTemplate来注册文档模板的对象,实现文档模板与应用程序关联。

(4) 创建应用程序的主边框窗口,程序代码为:

CMainFrame* pMainFrame = new CMainFrame; //创建对象

if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) //资源号

return FALSE; //调用成员函数创建主边框窗口

m_pMainWnd = pMainFrame;

只有产生了一个边框窗口后,才能产生文档边框窗口和视图窗口。这两个窗口都是主边框窗口的子窗口。

(5) 处理命令行并使窗口可见,程序代码为:

CCommandLineInfo cmdInfo; //定义命令行对象

ParseCommandLine(cmdInfo); //分析命令行信息,并规范化参数

if (!ProcessShellCommand(cmdInfo)) //执行命令行指定的缺省操作

return FALSE;

pMainFrame->ShowWindow(m_nCmdShow);//显示窗口

pMainFrame->UpdateWindow(); //更新窗口

窗口被产生并经初始化后,窗口仍不可见,只有调用成员函数 ShowWindow 和UpdateWindow后,才能使窗口在屏幕上显示出来。

15.1.5.2 成员函数Run和OnIdle

WinMain在完成初始化后,调用CWinApp的成员函数Run处理消息循环。当Run检查到消息队列为空时,将调用CWinApp的成员函数OnIdle进行空闲时的后台处理工作。若消息队列为空并且没有后台工作要处理时,则应用程序一直处于等待状态。一直等到有事件发生时为止。

15.1.6 窗口类

MFC中的窗口均是由CWnd类派生的。WINDOWS的窗口是通过窗口句柄(用一个无符号整数来标识)来控制和管理的,MFC中的窗口本质上是在WINDOWS窗口的基础上进行封装和派生的,应用程序很少用到窗口句柄,但MFC仍要通过窗口句柄来控制和管理窗口,为此,在产生窗口对象时,总是将窗口句柄存放在成员变量m_hWnd中。

CWnd类派生的主要窗口类有以下几种:

● CFrameWnd,它是SDI主边框窗口,形成单个文档及其视图的边框。

● CMDIFrameWnd,它是MDI应用程序的主边框窗口,所有的文档窗口都是它的子窗口,也把它称为所有文档窗口的容器。

● CMDIChildWnd,它是MDI子窗口,每一个文档及其视图都有一个MDI子窗口,与主边框窗口共享菜单。

●CView——视图窗口,它位于SDI主边框窗口或MDI子窗口的客户区中,它是主边框窗口的子窗口。