2023年7月17日发(作者:)
第三章 MFC应用程序概述
第3章 MFC应用程序概述
精讲
Microsoft Windows是微软公司推出的一个应用于微机上的具有图形用户界面的多任务和多窗口的操作系统。Windows应用程序也称为窗口应用程序,所有的窗口应用程序都有着相同的窗口风格和菜单结构,用户界面友好,方便用户操作。本章从剖析窗口应用程序的基本结构入手,继而介绍使用MFC类库开发的应用程序框架结构,并介绍窗口应用程序运行的核心机制-消息映射。学习了本章,你将对MFC应用程序框架结构和运行机制有个整体的了解,为后面进入窗口应用程序开发打下良好的基础。
3.1 窗口应用程序概述
窗口应用程序的开发一般采用可视化的面向对象的开发,可选择的窗口应用程序开发语言有Visual
C++、Visual Basic、Visual Java、Dephi等等。无论采用哪一种开发语言,首先要了解窗口应用程序的基本机制。
3.1.1 窗口编程基础
窗口应用程序运行于Windows操作系统,Windows操作系统是一个多任务操作系统,因此窗口应用程序的组成,支持技术,基本运行机制等与DOS应用程序有着本质的区别。在学习开发窗口应用程序之前,先要对窗口应用程序有一个概念上的了解。
1. 窗口
窗口是应用程序与用户进行交互的界面,应用程序通过窗口传递信息给用户,同样用户通过窗口输入数据,发布命令给应用程序。Windows界面包含了丰富的标准用户界面元素,包括窗口、图标、菜单、滚动条、对话框、控件和消息框等。用户使用这些界面元素可以方便的与应用程序进行交互,一个典型的窗口外观如图3-1所示。
最小化按钮
标题栏 菜单栏
关闭按钮
最大化按钮
控制菜单栏
客户区
垂直滚动条
1
窗口边界
水平滚动条 VC++6简明教程
图3-1 Windows应用程序窗口组成
在Windows编程中,各种窗口、菜单、按钮、对话框及程序模块等Windows的规范部件是按“对象”来组织的。为了提高开发窗口应用程序的效率,微软公司为用户提供了大量能创建上述标准元素的API函数和C++类,并且以Windows API函数库和C++类库的形式提供给用户,以充分满足构成应用程序操作界面的需要。
因此,要编写窗口应用程序必须了解这些标准对象的属性及方法,这样程序员的大量工作简化为创建对象和为对象属性赋值。标准对象具有标准的形态及标准的操作方法,并且能够对鼠标或键盘操作产生标准的消息响应。在后面的章节中将会陆续介绍上述窗口界面元素的创建原理和创建方法。
一个窗口应用程序可能包含一个或多个窗口,应用程序的运行过程即是窗口内部、窗口与窗口之间、窗口与系统之间进行数据处理与数据交换的过程。
2. 消息和消息队列
窗口应用程序是利用消息(Message)与其它窗口应用程序和操作系统进行信息交换的。消息的作用是通知一个应用程序某个确定的事件的产生,应用程序会对该事件产生响应,响应的方式已预先在应用程序中定义,即编写了相应的消息处理代码。例如当按下鼠标左键时,系统会产生WM_LBUTTONDOWN消息,并通知应用程序窗口,应用程序接到该消息后,会检查是否已定义消息处理函数并作出响应。
Windows操作系统内核基本元件USER为所有的用户界面元素提供支持,它用于接收和管理所有输入消息、系统消息,并把它们发送给相应应用程序的消息队列。消息队列是一个系统定义的内存块,用于临时存储消息,或是把消息直接发给窗口过程。每个应用程序都维护着自己的消息队列,并从中取出消息,利用窗口函数进行处理。如图3-2所示。
输入消息 系统消息
Send
Message
应用程序1
App1消息队列
消息映射
系统队列
App2消息队列
窗口函数
WndProc
App3消息队列
post
Message
默认窗口函数
DefWndowProc
图3-2 消息驱动模型
窗口应用程序接受系统队列传递过来的消息的步骤如下:
2 (1) 每个窗口应用程序都有一个WinMain()函数,在该函数中会定义一个窗口句柄,当窗口应用程序启动时,会使用窗口句柄注册,操作系统使用窗口句柄与窗口应用程序通信。
(2) 窗口应用程序创建一个或多个窗口,每一个窗口都有一个窗口处理函数(WndProc),负责窗口显示和响应用户输入。
(3) 消息映射(Message Loop)负责从消息队列中取消息,并送回窗口,由窗口处理函数选择合适的消息处理函数响应消息。若窗口处理函数中没有给出该消息的处理代码,将由DefWindowProc函数进行默认处理。
消息机制是窗口应用程序运行的核心工作机制,消息往往用一个如下的结构体MSG来表示,其定义如下:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
其中结构成员说明如下:
(1) hwnd
该消息所在的窗口句柄,若此参数为null,则可检索所有驻留在消息队列中的消息。
(2) message
消息值,每个Windows消息都有一个消息值,该值由Windows.h头文件中的宏定义来标识。
(3) wParam和lParam
包含有关消息的附加信息,它随消息的不同而不同。
(4) time
指定消息送至队列的时间。
(5) pt
指定消息发送时屏幕光标的位置,其数据类型POINT也是一个结构体,其定义如下:
typedef struct tagPOINT
{
LONG x;
LONG y;
} POINT;
Windows应用程序的消息来源有以下4种:
(1) 输入消息
由键盘和鼠标操作产生输入消息。这一类消息首先放在系统消息队列中,然后由Windows操作系统将它们送入应用程序消息队列中,由应用程序来处理消息。
(2) 控件消息
用户操作窗口的控件对象时产生控件消息, 例如当用户在列表框中改动当前选择或改变了复选框的状态时就会发出控件消息。这类消息一般不进入应用程序消息队列,而是直接发送到控件对象所属的对话框窗口。
3 VC++6简明教程
(3) 系统消息
对程序化的事件或系统时钟中断做出反映。一些系统消息,象DDE消息(动态数据交换消息)要通过Windows的系统消息队列,而另一些系统消息则不通过系统消息队列,而直接送入应用程序的消息队列,如创建窗口消息。
(4) 用户消息
这是程序员自己定义并在应用程序中主动发出的,一般由应用程序的某一部分内部处理。
3. 事件驱动的程序设计
Windows操作系统下的窗口应用程序采用事件驱动的程序设计,与DOS操作系统下的面向过程的程序设计有着明显的不同。
在DOS操作系统下,用户在DOS提示符后输入一行命令及参数,启动应用程序并按程序指定运行。一个程序是一系列预先定义好的操作序列的集合。程序直接控制程序事件和过程的顺序,并按照某种不可改变的模式进行工作。
在窗口应用程序下,用户首先启动应用程序,然后窗口等待用户通过对图形界面上的元素的操作,传递信息和命令,如选择一个菜单项或者单击一个按钮。窗口应用程序不是按事先安排好的顺序来执行的,而是由事件的发生来控制逻辑。事件的发生是随机的、不确定的,这样就允许用户按各种合理的顺序来安排程序的流程。
例如,有这样一个简单的应用程序,程序的功能是计算体操全能比赛项目运动成绩管理,体操全能包括四个项目。在一个过程驱动的程序中,要按照如下步骤完成应用程序功能的实现。
(1) 输入第一项运动员的成绩。
(2) 输入第二项运动员的成绩。
(3) 输入第三项运动员的成绩。
(4) 输入第四项运动员的成绩。
(5) 计算每个运动员的总分。
(6) 按总分排名。
在过程驱动的程序中,程序执行的顺序是确定的,如图3-3所示。
开始
输入第一项运动员的成绩
输入第二项运动员的成绩
输入第三项运动员的成绩
输入第四项运动员的成绩
计算每个运动员的总分
按总分排名
结束
图3-3 过程驱动程序流程示例
4
在这种过程驱动的程序中,首先按事先安排好的顺序把所有的成绩输入,然后再计算每一个运动员的总分,再进行成绩排名。
事件驱动的程序的逻辑顺序是按事件的产生而决定的,事件的产生不是预先定义的,有着随机性。图3-4给出了事件驱动程序的流程示意图。
开始
输入第一项某运动员的成绩
计算每个运动员的总分
消息
输入第二项某运动员的成绩
循环
输入第三项某运动员的成绩
输入第四项某运动员的成绩
按总分排名
结束
图3-4 事件驱动程序流程示例
事件驱动的应用程序启动后,每一个事件的发生将在对应的消息队列中放置一条消息,这样基于事件产生的输入没有固定的顺序,用户可以随机选取,以任何合理的顺序来输入数据。程序开始运行时,处于等待用户输入事件状态,然后取得消息并作出相应反应,处理完毕又返回并处于等待事件状态。在这种处理逻辑中,用户可以按需要进入不同的事件处理,例如,可以录入任意一个比赛项目,某位运动员的成绩,而不需要考虑录入顺序,随时计算运动员当前的总分和排名。体操比赛通常是几个项目同时进行,在比赛进行中,可以实时地输入运动员的得分,随时反映每个运动员的当前得分和当前总分排名情况。
4. 资源管理
资源可分为用户自定义资源和系统资源两种。用户自定义资源是指窗口可视元素的映像,即在前面所提及的窗口所包含的标准元素。例如菜单、对话框、工具栏等,是由每一个应用程序各自定义的。系统资源是由系统提供给各个应用程序共享的,常见的系统资源包括:设备上下文,画刷、画笔、字体、通信端口等。
由于Windows操作系统是一个多任务的操作系统,多个应用程序要共享系统资源,而不能象DOS程序独占系统的全部资源。系统资源是有限的,窗口应用程序使用资源的模式为:请求资源,使用资源,释放资源。如果系统资源在使用后不能得到及时的释放,会影响其它应用程序使用资源,最终造成死机。系统资源以句柄来标识,使用系统资源要通过Windows API函数来实现安全访问。
在窗口程序设计中,用户自定义资源与程序是分开定义的,在Develop Studio中提供了资源管理器用于用户自定义资源的创建、修改和维护。用户可以使用资源管理器提供的模板来设计资源,保存在资源文件中,资源文件通常以.RC为后缀名。每一个用户自定义资源都有唯一的ID标识,可以用一个自定义的整数或一个名称来表示。资源编译程序最终会将资源编译为应用程序所能读取的对象的二进制映像和具体的数据结构,并存放于应用程序的可执行文件中或动态连接库中,这样减轻了程序设计中定义和管理资源的复杂性。
5 VC++6简明教程
3.1.2 Windows应用程序组成
在这一节中将分析一个简单的Win32窗口应用程序,MFC应用程序是建立在Win32应用程序设计的基础上的,这将帮助更好地理解一个MFC应用程序是如何初始化的,窗口是如何创建的,消息是如何处理的。
所有的窗口应用程序必须包含两个基本函数:
(1) 应用程序主函数WinMain()
WinMain函数定义了窗口句柄,创建初始化窗口并启动一个消息循环。
(2) 窗口处理函数WinProc()
WinProc函数处理所有从操作系统传递到窗口的消息。每一个窗口,无论是简单的或复杂的,都要有一个窗口处理函数。
【例3-1】创建一个简单的窗口应用程序示例。本例的目的在于说明创建Windows应用程序的方法及过程。
(1) 在VC集成开发平台,执行菜单“File”下的菜单命令“New”,打开“New”对话框。
(2) 在“New”对话框中的“Project”标签页中选择“Win32 Application”,在右边输入工程名为:Exam3_1,并确定工程文件保存位置,单击“OK”按钮。
(3) 在随后的向导窗口Win32 Application-step 1of 1中,选择“An empty project”,单击“Finish”按钮。
(4) 在出现的“New Project Information”对话框中,单击“OK”按钮,完成工程的创建,并回到集成开发平台。
(5) 再执行菜单“File”下的菜单命令“New”,添加一个新的C源文件(*.cpp),程序名为Exam3_1。
(6) 按程序清单3-1中的代码实现Exam3_文件。
(7) 编译运行。
程序清单3-1:创建一个简单的Windows窗口程序代码
//包含应用程序中所需的数据类型和数据结构的定义
#include
//窗口函数说明
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
//--------------- 以下初始化窗口类 ----------------------
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hwnd ;
MSG Msg ;
WNDCLASS wc;
char szApplicationName[]= "Exam3_1Window"; //窗口标题名
//定义窗口类
=CS_HREDRAW|CS_VREDRAW; //定义窗口类型为当窗口大小变化时窗口重画
dProc=WndProc; //定义窗口处理函数
6 xtra=0; //窗口类无扩展
xtra=0; //窗口实例无扩展
nce=hInstance; //当前实例句柄
=LoadIcon(NULL,IDI_APPLICATION);
//窗口的最小化图标为缺省图标
r=LoadCursor(NULL,IDC_ARROW) ;
//窗口采用箭头光标
kground=(HBRUSH)GetStockObject(WHITE_BRUSH);
//窗口背景为白色
nuName=NULL; //窗口中无菜单
assName= szApplicationName;//定义应用程序标题
//------注册窗口类-------
RegisterClass( &wc);
//---------------- 创建窗口 -------------------
hwnd=CreateWindow
(
szApplicationName, //窗口类名
szApplicationName, //窗口的标题名
WS_OVERLAPPEDWINDOW, //窗口的风格
CW_USEDEFAULT,
CW_USEDEFAULT, //窗口左上角坐标为缺省值
CW_USEDEFAULT,
CW_USEDEFAULT,, //窗口的高和宽为缺省值
NULL, //此窗口无父窗口
NULL, //此窗口无主菜单
hInstance, //创建此窗口的应用程序的当前句柄
NULL
);
//--------------- 显示窗口 ----------------------
ShowWindow( hwnd, nCmdShow) ;
//-------------- 绘制用户区 ---------------------
UpdateWindow(hwnd);
//----------------- 消 息 循 环 ----------------------
while( GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage( &Msg) ;
DispatchMessage( &Msg) ;
}
return ; //消息循环结束即程序终止时将信息返回系统
}
//------------------------窗口函数-----------------
LRESULT CALLBACK WndProc ( HWND hwnd,
7 VC++6简明教程
UINT message,
WPARAM wParam,
LPARAM lParam
)
{ switch(message)
{ case WM_DESTROY:
PostQuitMessage(0);// 调用PostQuitMessage发出WM_QUIT消息
default: //缺省时采用系统消息缺省处理函数
return DefWindowProc(hwnd,message,wParam,lParam);
}
return 0;
}
上述程序的运行结果如图所示:
图3-5 程序运行结果图
1. WinMain函数
和C语言中的main()函数一样,Windows程序是从WinMain()函数开始的,并随着WinMain函数的结束而结束的。WinMain()函数是在WINBASE.H中定义的,定义如下:
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
参数说明如下:
(1) hInstance
系统分配的窗口所属的应用程序的实例句柄,标识当前进程的实例,它实际上是进程所占据的地址空间的首地址,这个实例句柄是程序的唯一标识。
(2) hPrevInstance
8 用做检查是否有多个程序实例运行,这是16位Window的残留物,在Win32应用程序中,这个参数始终为NULL。
(3) lpCmdLine
是一个指向字符串的指针,用来保存运行程序时的命令行参数,这同main()函数中的argv[]参数的作用类似。
(4) nCmdShow
用来指明应用程序的主窗口的显示方式(最大化显示,最小化显示,一般化显示)。该参数经常被ShowWindow()函数作为显示窗口的参数。
WinMain()函数的作用是完成以下三个任务:注册窗口类;创建并初始化窗口;创建消息循环。
(1) 注册窗口类
每个窗口都包含着一些基本的属性,如:窗口边框、窗口标题栏文字、窗口大小和位置、鼠标、背景色、处理窗口消息函数的名称等等。注册的过程就是将这些属性告诉系统。
窗口应用程序使用结构体WNDCLASS描述窗口类型,它描述了窗口的样式、窗口消息处理函数、程序句柄、图标、光标、背景刷、菜单以及描述本窗口类型的结构的名称。首先使用数据结构WNDCLASS定义窗口的各个属性,再使用Windows 的API函数RegisterClass()注册窗口类。结构体WNDCLASS定义如下:
typedef struct tagWNDCLASS{
UNIT style;//指定窗口格局的整型数,
WNDPROC lpfnWndProc;//负责控制窗口、处理窗口消息的窗口函数,
int cbClsExtra;//为指定这个窗口类别结构额外分配的字节数,一般设为0。
int cbWndExtra;//为指定这个窗口类别中所有窗口结构额外分配的
字节数,一般设为0。
HANDLE hInstance;// 标志要创建的窗口所属应用程序的句柄。
HICON hIcon;// 指定窗口的最小化图标句柄。
HCURSOR hCursor;// 窗口中所使用的光标的句柄。
HBRUSH hbrBackground;// 用来画窗口背景的画刷的句柄。
LPCWSTR lpszMenuName;// 标志窗口中的菜单资源名字的字符串。
LPCWSTR lpszClassName;// 标志该窗口类别的名字的字符串。
} WNDCLASS;
RegisterClass()函数接收的是一个WNDCLASS结构的指针。如果注册成功,RegisterClass()返回TRUE,否则返回FALSE。只有注册成功后,才能创建窗口。
(2) 创建并初始化窗口
如果窗口类注册成功,下一步,便可以使用该窗口类创建窗口。CreateWindow()函数用于创建窗口,该函数进一步定义了窗口的名称,位置,尺寸等数据。如果创建成功,则返回一个系统分配的窗口句柄,否则返回0。CreateWindow()函数定义如下:
HWND CreateWindow(
LPCTSTR lpszClassName, ∥窗口类名
LPCTSTR lpszTitle, ∥窗口标题名
DWORD dwStyle, ∥创建窗口的样式
int x,y, ∥窗口左上角坐标
int nWidth,nHeight, ∥窗口宽度和度高
HWND hwndParent, ∥该窗口的父窗口句柄
9 VC++6简明教程
HWENU hMenu, ∥窗口主菜单句柄
HINSTANCE hInstance, ∥创建窗口的应用程序当前句柄
LPVOID lpParam ∥指向一个传递给窗口的参数值的指针
);
窗口创建成功后并没有显示出来,还需要调用ShowWindow()和UpdateWindow()两个函数来显示窗口,这两个函数定义如下:
ShowWindow (hwnd, nCmdShow );
UpdateWindow (hwnd );
ShowWindow()函数的作用是显示或隐藏窗口,第一个参数是要显示的窗口的句柄,第二个参数表示窗口的显示样式,如SW_SHOW表示正常显示,SW_HIDE表示隐藏。UpdateWindow()函数的作用是更新当前窗口显示。
(3) 创建消息循环
WinMain()函数最后使用while语句创建一个消息循环,负责从消息队列中获取消息,并派送消息到相应的处理位置。最简单的消息循环由TranslateMessage()和DispatchMessage()函数组成,格式如下:
while (GetMessage (&Msg,NULL,0,0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
GetMessage()函数负责检查应用程序的消息队列,一旦发现队列中有消息,便取出一条消息,把它复制到MSG结构体变量中,同时该函数返回TRUE。TranslateMessage()函数的作用是将来自键盘的命令翻译为消息的ID字符表示。DispatchMessage()函数的作用是把每个消息分发给相应的窗口函数。当检索到WM_QUIT消息时,程序结束循环并退出。
2. 窗口处理函数
注册窗口类的一个主要目的就是将一个窗口和一个窗口处理函数联系起来,窗口处理函数决定窗口在它的客户区中显示什么,和窗口如何响应用户输入。窗口处理函数能够按程序员预先定义的代码处理消息,如果程序员没有对该消息的处理作定义,则把此消息交给默认窗口处理函数DefWindowProc()处理。函数DefWindowProc()是系统默认的处理过程,以保证所有发送到该窗口的消息均得到处理。窗口函数定义如下:
LRESULT CALLBACK WndProc(
HWND hwnd,
UNIT message,
WPARAM wParam,
LPARAM lParam )
说明如下:
(1) LRESULT
表示函数返回值为长整数,由系统使用。
(2) CALLBACK
表示该函数是回调函数,由系统调用。
(3) hwnd
该参数是接收消息的窗口句柄,它和CreateWindow()函数的返回值相同。
10 (4) message
该参数是用来标识该消息的数字。
(5) wParam和 lParam
两个参数是32位的消息参数,用来提供消息的附加信息。
在例3-1中窗口处理函数的名称为WndProc,这个名称可以由用户自己在窗口类的lpfnWndProc属性中指定,例如可改为MyWndProc。
在窗口函数中,根据接收的消息信息(message,wParam和lParam)进行判断,然后分门别类地进行处理。窗口函数中使用switch语句定义对应用程序接收到的不同消息的响应,包含了对各种可能接收到的消息的处理过程。每一条case语句对应一种消息,当应用程序接收到一个消息时,相应的case语句被激活并执行相应的响应程序模块。程序员可以在switch语句中增加对各种消息的处理代码。WM_DESTROY消息是关闭窗口时发出的,处理方法是调用函数PostQuitMessage() 向应用程序发出WM_QUIT消息,请求退出处理函数。
MFC应用程序是建立在窗口应用程序开发模式的基础上的,同样MFC应用程序也有一个WinMain()函数,但程序员不用编写该函数,WinMain()函数由框架提供,当应用程序启动时被自动调用。MFC有一个内部的消息系统处理由类产生的绝大多数消息。当一个消息不能被 MFC所处理,应用程序将它交给默认窗口处理函数DefWindowProc()来处理。
3.1.3 应用程序举例
【例3-2】分析使用AppWizard生成的Win32窗口程序示例Exam3_2,并对该程序作以下修改:
(1) 修改输出文本串“Hello World!”为”Hello VC++!”。
(2) 在视图的中央输出文本“starting your VC++ learning !”。
(3) 添加一个对话框,并能使用菜单调用这个对话框。
分析HelloWorld程序:
在第一章的实践2中已描述了使用AppWizard快速生成Win32窗口程序示例的步骤,下面对同样方法生成的Exam3_2程序进行分析:
1. WinMain函数
程序清单3-2:Exam3_2中的WinMain函数
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_EXAM3_2, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);//注册窗口类句柄
11 VC++6简明教程
// 初始化应用程序
if (!InitInstance (hInstance, nCmdShow)) //创建并显示窗口
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_EXAM3_2);//装载快捷键
// 主消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return ;
}
如程序清单3-2所示,WinMain()函数的构成同样包含三个主要过程:
(1) 通过MyRegisterClass()函数定义窗口类句柄,并返回RegisterClassEx()函数结果;
(2) 通过InitInstance()函数调用CreateWindow()创建窗口,并显示更新窗口;
(3) 创建消息循环,接收消息并分发消息。
LoadString()函数的作用是加载字符串资源,字符串资源是在字符串表中定义的,如图3-6所示,IDS_APP_TITLE表示应用程序的名称,加载到szTitle数组,IDC_EXAM3_2表示Windows默认类的标识名,加载到szWindowClass数组,在定义窗口和创建窗口的时候调用。
图3-6 HelloWorld中的字符器资源定义
2. 窗口函数WndProc
程序清单3-3:Exam3_2中的WndProc函数和About函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
12 LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
//分派窗口消息处理
switch (message)
{
case WM_COMMAND: //处理菜单消息
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox (hInst, (LPCTSTR)IDD_ABOUTBOX,
hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT: //处理窗口重绘消息
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //处理应用程序退出消息
PostQuitMessage(0);
break;
default: // 启动默认窗口处理函数
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
13 VC++6简明教程
//About对话框的消息处理函数
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM
lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
如程序清单3-3所示,窗口函数WndProc()是处理各种消息,在这个例程中处理三类消息:
(1) WM_COMMAND
WM_COMMAND是操作菜单项、加速键或工具按钮发出的命令消息,其中窗口消息包包含的WParam字段的低16位为命令ID,变量wmId获得命令ID后,使用switch语句继续对不同的命令消息进行处理。
IDM_EXIT是菜单命令“Exit”的ID,处理方法是调用DestroyWindow()函数了发出WM_DESTROY消息。
IDM_ABOUT是菜单命令“About”的ID,处理方法是调用DialogBox()函数,打开对话框窗口。在使用DialogBox()函数时,需要指定对话框资源ID和对话框消息处理函数。在本例中,对话框资源IDD_ABOUTBOX,对话框消息处理函数是About()函数。
对话框消息处理函数的构造与窗口函数WndProc()类似,通过一个分情况语句响应不同的消息。在About()函数中对对话框的初始化消息WM_INITDIALOG和命令消息WM_COMMAND进行了处理。对话框运行时,单击“OK”按钮就发出消息IDOK,单击“Cancel”按钮时发出IDCANCEL,系统定义的IDOK和IDCANCEL的消息处理中,都调用了EndDialog()函数关闭对话框窗口。
(2) WM_PAINT
WM_PAINT是窗口重绘消息,当窗口大小发生变化、移动、滚屏、菜单覆盖视图、鼠标掠过视图等任何使视图显示改变的情况下,都会发出WM_PAINT消息。本例的处理是在调用DrawText()函数在视图的顶部中央输出文本串szHello,szHello的内容是通过LoadString函数装载的字符串资源IDS_HELLO。
(3) WM_DESTROY
退出应用程序的消息处理,调用PostQuitMessage()函数,结束消息循环,WinMain()函数运行结束。
按要求修改示例程序:
1. 修改输出文本串“Hello World!”为”Hello VC++!”
14 (1) 展开工作区ResourceView面板中的String Table资源,双击打开String Table。
(2) 双击IDS_HELLO,弹出String Properties对话框,在Caption中修改字符串的内容,即输入“Hello
VC++!”。
(3) 重新编译运行程序。
2. 在视图的中央输出文本“starting your VC++ learning !”
(1) 在FileView面板中展开Source Files文件夹,双击Exam3_文件打开,找到WndProc()函数。
(2) 在WM_PAINT消息的处理中添加文本输出函数语句如下:
DrawText(hdc,"starting your VC++ learning!",28,&rt,
DT_SINGLELINE|DT_CENTER|DT_VCENTER);
(3) 重新编译运行程序。
3. 添加一个对话框,并能使用菜单调用这个对话框
(1) 执行菜单命令Insert->Resource,在弹出的对话框中选择Dialog,单击按钮“New”,系统会添加一个新的对话框资源IDD_DIALOG1到ResourceView面板中的Dialog文件夹中。
(2) 双击IDD_DIALOG1打开对话框编辑器窗口,在窗口任意空白处右击鼠标键,在打开的快捷菜单中选择Properties,在弹出的Dialog Properties窗口中的Caption内容修改为“MyDialog”,此即为修改后的对话框的标题栏名称。
(3) 在工作区的ResourceView面板中,展开Menu菜单资源,双击IDC_EXAM3_2,打开菜单编辑器窗口,在Help菜单中添加一个菜单项。即双击Help菜单下的空白处,在打开的Menu Item
Properties窗口中输入,Caption内容为:MyDialog,ID号定义为:IDM_MYDIALOG。
(4) 在WndProc()函数对WM_COMMAND窗口消息的情况处理分支中添加对IDM_MYDIALOG菜单消息的处理代码如下,调用DialogBox打开IDD_DIALOG1对话框:
case IDM_MYDIALOG:
DialogBox(hInst, (LPCTSTR)IDD_DIALOG1, hWnd, (DLGPROC)Mydialog);
break;
(5) 在Exam3_文件的最后添加对话框消息处理函数Mydialog函数,代码如程序清单3-4所示。
程序清单3-4:对话框消息处理函数Mydialog函数
LRESULT CALLBACK Mydialog(HWND hDlg, UINT message, WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
15 VC++6简明教程
return FALSE;
}
(6) 在Exam3_文件的头部的函数声明处添加函数声明语句如下:
LRESULT CALLBACK Mydialog(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
(7) 重新编译运行程序,执行菜单命令MyDialog,打开自定义的对话框。最后运行效果如图3-7所示。
执行菜单命令MyDialog,打开的自定义对话框
图3-7 例3-2程序运行效果
3.2 MFC应用程序框架
利用Windows API函数编制Windows应用程序, 程序员需要编写大量的程序代码。而利用MFC编写实现同样功能的程序,则要轻松得多。在第一章中我们学习了使用AppWizard创建一个MFC单文档应用程序,不需要做任何额外的工作,直接运行程序,一个具备常用工具栏、菜单栏、状态栏的标准窗口应用程序就已经出现的屏幕上。
MFC(Microsoft Foundation Class)是由微软编写的一套专门用于Windows编程的C++基础类库,其内容很广泛,功能也相当强大,VC编程基本上都是围绕着MFC类库来进行的,它封装了Windows API的绝大多数功能,通过用户开发Windows应用程序建立了一个非常灵活的应用程序框架。
但习惯使用SDK编写窗口应用程序的程序员,初次接触MFC编程时都会有这样困惑:找不到WinMain函数,应用程序如何执行?程序的执行似乎不在程序员的掌握之中。所以如何理解MFC应用程序框架是学习VC的核心任务之一。
3.2.1 MFC类简介
MFC类库中的各个类支持快速生成面向对象的应用程序,它们之间存在一定的关联,它们的集合构成MFC应用程序框架,如图3-8所示。
CObject
CCmdTarget
CWinThread
CWinApp
16
CWnd
CFrameWnd
CDocument
图3-8 MFC类的继承关系
CObject是MFC类库的根类。从CObject派生的类都具有以下特点:
(1) 在程序运行时,可获得对象的大小、类名、动态创建类的实例。
(2) 提供了把对象状态转储给调试机制的能力,类似于判断当前对象的数据成员是否有效。
(3) 具有把对象的数据存进文件、或从文件中提取数据重建对象的能力。
命令类CCmdTarget:是CObject的子类,它是MFC库中所有具有消息映射属性的基类。它的子类有CWinThread类,CWnd类、CDocument类,从CCmdTarget派生的类在程序运行时,能动态创建对象,并能处理命令消息。
应用程序线程支持类CWinThread:MFC支持多线程,所有的应用程序至少有一个线程。CWinThread是所有线程类的基类,封装了操作应用程序的多线程功能。应用程序类CWinApp是CWinThread的子类,封装了初始化、运行、终止应用程序的代码。
窗口类CWnd:提供了MFC中所有窗口类的基本功能。从CWnd派生的类可以拥有自己的窗口,并能对它进行控制。框架窗口类CFrameWnd和视图类CView是CWnd类的两个子类,前者提供和维护窗口的边框、菜单栏、工具栏、状态栏,负责显示和搜索用户命令,后者负责为文档提供一个或几个视图。视图的作用是为修改、查询文档等任务提供人机交互的界面。
文档类CDocument:负责装载和维护文档。文档包括应用程序的工作成果或环境设置数据等,或是程序需要保存的任何内容。
一个MFC应用程序并不直接操作上述类,而是从上述类为基类派生新的类,构建窗口应用程序的基本框架。例如应用程序Exam1_1中的类与这些基类的派生如表3_1所示。
表3-1基类与派生类的关系
类名称
CExam1_1App
CMainFrame
CExam1_1Doc
CExam1_1View
CWinApp
CFrameWnd
CDocument
CView
基类 说明
应用程序类
窗口框架类
文档类
视图类
3.2.2构建窗口应用程序的基本类
利用VC++提供的AppWizard,可直接生成MFC应用程序。但无法看到Windows程序结构中的主函数WinMain和窗口函数WndProc,因为它们被封装在MFC类中了。在MFC类中,应用程序类CWinApp和框架窗口类CFrameWnd替代了主函数WinMain和窗口函数WndProc的功能,负责程序的初始化,退出时必要的清理,窗口的创建和消毁,消息循环等功能。
应用程序类CWinApp和框架窗口类CFrameWnd是MFC应用程序最基本的两个类,所有的MFC应用程序都必须包含这两个类,下面以第一章建立的应用程序Exam1_1分析这两个基本类的主要功能。
1. 应用程序类CExam1_1App
17 VC++6简明教程
在AppWizard生成的五个类中,CExam1_1App代表的是应用程序,它的基类CWinApp代表了Windows应用程序,并把每一个程序都看做一个对象。应用程序类将负责完成MFC应用程序的一些例行初始化工作,另外,CWinApp的基类是CWinThread,因此它也要负责管理程序主线程的运行。
AppWizard声明一个由CWinApp继承的类CExam1_1App,在生成的实现文件中包含:
(1) 应用程序类的消息映射
(2) 一个空的应用程序类的构造函数
(3) 一个CExam1_1App类的全局对象theApp
(4) 一个InitInstance函数的标准定义
全局对象theApp,在WinMain()函数执行之前初始化,在进入MFC版本的WinMain()函数之后,theApp很快就获得了管理权,进行Windows程序的一些例行初始化工作。
CWinApp提供了四个函数管理实现MFC应用程序的生命期,函数功能如表3_2所示,这四个函数都能被CWinApp的派生类CExam1_1App重载,其中只有InitInstance()函数是必须被重载的。
表3-2 CWinApp可重载的成员函数
成员函数
InitInstance
Run
ExitInstance
OnIdle
说 明
负责应用程序的初始化工作,创建文档模板,文档,视图和主窗口,该函数是唯一一个必须重载的函数
初始化结束后,由WinMain函数调用处理消息循环,一个文档/视图应用程序绝大多数时间处于Run函数的执行过程中
当用户退出程序的时候,该函数被调用
当没有窗口消息需要处理时由窗口框架调用,通常用于执行后台任务
在CExam1_1App类中,InitInstance()函数首先调用AfxEnableControlContainer()函数,允许程序使用ActiveX控件,接着又调用Enable3dControls()函数或Enable3dControlsStatic()函数,允许程序使用具有3D效果的控件。SetRegistryKey()函数和LoadStdProfileSettings()函数的作用是从注册表中读取与本程序有关的信息,这些信息在第一次运行本程序时自动建立,以后在需要时会自动更新。
接下来的几条语句是为程序定义一种文档模板类型,文档模板把文档类CExam1_1Doc、主框架类CMainFrame和视图类CExam1_1View联系在一起。
ParseCommandLine()和ProcessShellCommand()两个函数分别用于分析和处理命令行参数,如果想让程序支持自定义的命令行参数,可以在此添加自己的处理代码。最后两条语句把程序唯一的主窗口显示出来,并进行更新。
2. 主框架类CMainFrame
CMainFrame代表的是程序的主框架窗口,一个窗口应用程序除了白色部分的视图(View)外,程序主窗口的其它部分都归CMainFrame管理。视图实际上是主框架窗口的子窗口,它的大小正好等于主框架窗口的客户区的大小,当运行一个多文档程序,例如Word,将所有的文档窗口最小化,就会留下一个灰色背景的客户区,它也是主框架窗口的一部分。
单文档程序的主框架窗口是在调用ProcessShellCommand()函数时创建的,在主框架窗口被创建之前,CMainFrame::PreCreateWindow()函数将被自动调用。在这个函数中可以更改主框架窗口的风格,或者对窗口类的一些属性进行修改,此时窗口句柄还不可用。
当Windows通过Win32函数CreateWindowEx()接收到创建主框架窗口的请求时,它会在系统内部为窗口分配资源,并进行一些设置工作,此时窗口句柄就可以使用了,当Windows从CreateWindowEx()函数返回之前,它向程序发送WM_CREATE消息,让程序完成一些必要的初始化工作,CMainFrame::OnCreate()函数就是WM_CREATE消息的处理函数,这个函数首先调用了基类的处理函数,18 让基类完成初始化工作,然后为主框架窗口创建工具栏和状态栏。
CMainFrame的两个成员变量m_wndToolBar和m_wndStatusBar分别对应着程序主窗口中的工具栏和状态栏。工具栏的创建方式很直接,CToolBar::LoadToolBar()函数负责装载工具栏资源,而CToolBar::CreateEx()函数将负责工具栏的创建。状态栏的使用需要一个数组来定义状态栏类各个窗格的ID,这个数组就是indicators。CStatusBar::Create()函数负责工具栏的创建,CStatusBar::SetIndicators()函数负责设置状态栏上的窗格。
CMainFrame还有两个成员函数AssertValid()和Dump(),它们只在调试版本中存在,可以利用它们来验证类的某些成员变量的值是否有效,以及向Output窗口中输出对象的内部状态。很多从CObject派生出来的类都重载了这两个函数,以帮助程序员调试程序。
本章实践与提高的实践1中,描述了手工创建一个最小MFC应用程序的过程,在这个应用程序中仅包含应用程序类和窗口框架类,说明只要包含这两个基本类,就能成功地构建一个窗口应用程序。
3.2.3 文档/视图结构
文档/视图结构是MFC应用程序最核心的概念,它将应用程序的数据和浏览、操纵数据的方法分离。简单地说,文档对象通常代表一个已经打开的文件,而视图对象表示文档中数据的可视化表示,并提供可视化交互界面允许用户查看、编辑数据。
1. 文档/视图结构
图3-9演示了一个MFC应用程序中各个主要对象在文档/视图结构中交互的途径。应用程序的数据存储在文档对象中,并可以显示在视图。文档与视图的关系是1对多的关系,也就是说文档中的数据可以以不同的方式显示。例如:在一个Excel文件中,同样的数据可以以表格的形式表示,也可以图表的形式表示。在一个视图中修改数据,首先反映到文档对象,刷新视图,在各个视图中反映所作的修改。
一个MFC应用程序开始运行后,应用程序对象负责传递消息到窗口框架对象和视图对象。文档对象负责管理数据,视图对象按特定的方式反映当前文档对象中数据,视图和框架同时提供可视化的界面与用户实现交互,用户使用鼠标和键盘操作应用程序的菜单、工具栏及控件,发出命令消息,输入信息,应用程序接受消息,接受输入数据,并以预先定义的方式反映用户的操作。
应用程序对象 窗口框架对象
①传递消息到窗口框架
②传递消息到视图
③
视图对象
①
②
文档对象
19 VC++6简明教程
③信息在视图与文档对象间双向传递
图3-9 文档/视图结构
2. 文档类
应用程序中的文档类是CDocument类的派生类,CDoument类主要的成员函数及功能如表3-3所示。
表3-3 CDoument的成员函数
成员函数
OnNewDocument
OnOpenDocument
DeleteContents
Serialize
UpdateAllView
SetModifiedFlag
IsModified
GetTitle
GetFirstViewPosition
GetNextView
GetPathName
说 明
初始化一个新的文档对象,当创建一个新文档时,系统自动调用,默认重载。
当从磁盘打开一个文件时被系统自动调用,可重载。
删除文档对象的内容。当文档被关闭时系统自动调用,可重载。
文档序列化函数用于从一个文件中读取内容到文档对象,或者将文档对象的内容保存到文件。默认重载。
更新与文档对象关联的所有视图,该函数自动调用每个视图对象的OnUpdate函数实现更新操作。
设置或清除文档的数据是否修改的标志,该标志决定执行应用程序关闭操作时,是否会弹出对话框确认是否需要保存文件。
如果文档对象包含未保存数据则返回一个非零值,否则返回一个零值。
返回表示文档标题的文本串,如果文档没有标题返回空串
返回一个POSITION类型的变量表示第一个视图的位置。
返回一个CView类型的指针,指向当前文档对象相关的一组视图中的下一个视图
返回与文档相关的文件的名称和路径
AppWizard默认重载了两个虚函数OnNewDocument()和Serialize()函数。
初始化文档有两种途径:一是执行菜单File下的菜单命令New,二是执行菜单File下的菜单命令Open,前者调用OnNewDocument()函数,后者调用OnOpenDocument()函数。此时,在SDI应用程序中,打开的文档将被关闭,一个新的空文档将加载到文档对象中;在MDI应用程序中,除了已打开的文档外,一个空文档将被打开。
Serialize()成员函数是将文件中的数据装入到文档对象或保存文档对象的数据到文件中去的地方。它的形式如程序清单3-5所示。
程序清单3-5:Serialize()的形式
void CExam1_1Doc::Serialize(CArchive& ar)
{
if (ing())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
20 }
当向文件写数据时,执行ing()分支,操作运算符“<<”能把数据存到ar指定的文档中去;当从文件读数据时,则执行另一分支,操作运算符“>>”能从文件读数据,并初始化相关成员变量。一般情况下,只有读取、保存文件时要调用Serialize函数。
3. 视图类
视图类显示存储在文档类对象中的数据,并允许用户编辑这些数据。应用程序中视图类是CView类的派生类,CView类的主要成员函数及功能如表3-4所示。
表3-4 CView的成员函数
成员函数
GetDocument
OnDraw
OnInitialUpdate
OnUpdate
说 明
返回一个指向相关的文档对象的指针,通过该指针,可以在视图类的成员函数中操作文档对象中的数据,实现输出。
支持打印,打印预览、和屏幕输出
当一个视图第一次与文档对象相关联时,由系统自动调用。
当文档对象的数据更新后,视图需要更新时调用。默认时对整个视图更新,重载该函数可编写代码仅更新部分视图。
GetDocument()函数是很重要的一个成员函数,用于视图类与文档类的通信,OnDraw()是实现视图输出的关键函数,绝大多数的视图输出工作都在这个函数中完成。当程序窗口创建、移动、改变大小或窗口覆盖时,窗口都需要重绘,系统会自动调用视图类的OnDraw(CDC* pDC)成员函数。其参数pDC是指向CDC类对象的指针,负责文档显示的设备环境,设备环境对象与特定的设备相关联,并有一组相应的成员函数。
应用程序的视图类可以直接从CView类中继承,也可以从视图类的派生类中继承,视图类的派生类提供了特殊功能支持,主要有如下几种:
(1) CScrollView
具有滚动功能的视图类的基类。
(2) CFormView
其布局在对话资源中定义的滚动视图类。
(3) CEditView
显示一个编辑控件,提供多行文本的编辑,具有文本编辑、查找、替换和滚动功能的视图类。
(4) CRichEditView
显示一个直通文本编辑控件的视图类。
(5) CListView
显示一个列表控件的视图类。
(6) CTreeView
显示一个树控件的视图类。
(7) CRecordView
支持对话框数据交换的视图类。
(8) CCtrlView
支持直接基于控件的视图,是CEditView、CRichView、CTreeView、CListView 的基类。
在使用AppWizard生成一个MFC应用程序时,可以选择视图类的基类,以获得MFC对一些复杂任务的支持,简化程序开发的复杂性。
4. 文档模板类
21 VC++6简明教程
AppWizard除了生成可在工作区中展示的应用程序类,窗口框架类,文档类和视图类以外,还生成了文档模板类CDocTemplate。文档模板类定义了文档模板的基本功能,是抽象基类,通常不需要程序员干涉它的运行,所以在工作区中没有列出文档模板类。
文档模板把文档类、主框架类和视图类联系在一起。MFC提供了它的两个派生类:单文档界面的文档模板CSingleDocTemplate 和多文档界面的文档模板CMultiDocTemplate,如图3-9所示。
CObject
CCmdTarget
CDocTemplate
CMultiDocTemplate CSingleDocTemplate
图3-10 文档模板类的层次图
前面的讲述中提到在应用程序类的InitInstance()成员函数中有如程序清单3-6所示的一段代码,其作用是为程序定义一种文档模板类型。
程序清单3-6:单文档应用程序中定义文档模板类型的代码
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CExam1_1Doc),
RUNTIME_CLASS(CMainFrame), //main SDI frame window
RUNTIME_CLASS(CExam1_1View));
AddDocTemplate(pDocTemplate);
这段代码首先创建了CSingleDocTemplate的一个对象实例,该对象是适用于单文档程序的文档模板。它的构造函数的第一个参数指定了在打开此类文档时使用的缺省资源(包括菜单、图标、加速键和字串等)的ID值。在ResourceView中看到很多ID值都是“IDR_MAINFRAME”但类型不同的资源,因为它们是一“套”的。构造函数的后三个参数都使用了RUNTIME_CLASS宏, RUNTIME_CLASS接受一个类名作为参数,返回指向一个CRuntimeClass结构的指针,从MSDN库中了解到,该结构可以用来在运行过程中获取一个对象的类及其基类的信息,并可以动态创建类的对象实例。CRuntimeClass结构实际上是RUNTIME_CLASS的参数中指定的类的一个静态成员。
CSingleDocTemplate对象被AddDocTemplate()函数加入到应用程序对象内部的一个文档模板链表中,多数单文档程序都只支持一种文档类型,因此链表中一般只存有一个文档模板对象。写字板程序是一个能处理多种文档类型的单文档程序,在这种情况下链表中就不止一个文档模板对象了。
如果没有在命令行中指定要打开的文件名,应用程序类的ProcessShellCommand()函数就会自动生成一个新文档。在生成新文档时,应用程序类将检查文档模板链表,如果链表中只有一个文档模板对象,那么就直接调用该对象的OpenDocumentFile()函数,如果链表中有多个文档模板对象,那么它会弹出一个对话框,让用户选择一种类型,然后调用所选文档模板对象的OpenDocumentFile()函数,这个函数将负责创建文档对象和主框架对象。
文档、主框架和视图三种对象的创建过程可以简述如下:文档模板对象首先创建一个文档对象,接下来又创建一个主框架对象,最后由主框架对象创建视图对象。在这个过程中,文档模板对象把文档对22 象和视图对象信息告诉了主框架对象,后者在创建视图对象时会告诉视图对象对应的文档对象,同时告诉文档对象新创建了一个视图对象,这样文档和视图就联系起来了。
前面所讨论的文档对象都只对应着一个视图对象,其实,一个文档对象可以同时对应多个视图对象,例如使用了分割窗口的程序,这些视图对象甚至可以属于不同的视图类,但是,一个视图对象只能对应一个文档对象。
还需要指出一点,在缺省情况下,不支持多种文档类型的单文档程序只在程序启动时创建一个文档对象(视图对象也一样),用户在程序运行过程中选择新建文档或打开文档命令时,程序不会将原来的文档对象删除,而是重新将原来的对象初始化。支持多种文档类型的单文档程序在程序启动时也只创建一个文档对象,当用户新建一个类型相同的文档时,程序会重复使用原来的对象,当用户要新建一个类型不同的文档时,程序就创建一个相应类型的新文档对象,这样内存中就同时存在两个可以重复利用的文档对象,它们在程序退出时才会被删除。多文档程序又不一样,它们在每次新建或打开文档时都会创建一个新的文档对象,每次关闭一个文档时又会将相应的文档对象删除。
3.2.4 剖析MFC SDI的文件结构
在第一章的应用程序Exam1_1中,AppWizard创建了一个完整的SDI应用程序,在指定的Exam1_1目录下创建了许多文件,这些文件包含了框架程序的所有类、全局变量的声明和定义。根据创建项目时提供的可选项,AppWizard所创建的文件会略有不同。标准的AppWizard文件包括:
1. 工作区文件、项目文件
(1) Exam1_:由MFC自动生成的工作区文件,保存了当前工作区所包含的项目的信息。
(2) Exam1_:MFC生成的项目文件,或叫工程文件,包含当前项目的设置、项目中包含的文件等信息。
(3) Exam1_:类信息文件。ClassWizard利用其信息编辑现有类或增加新类;并在其中保存创建和编辑消息映射和对话框数据所需的信息、创建虚拟成员函数所需的信息。
2. 应用程序源文件和头文件
根据应用程序的类型——单文档、多文档或基于对话框,AppWizard将自动创建一些应用程序源文件和头文件,这些文件分别是应用程序类、文档类、主窗口类和视图类的声明文件和实现文件。
在本例中,AppWizard生成了如下文件:
(1) Exam1_1.h:应用程序的主头文件,含有所有全局符号和用于包含其他头文件的#include伪指令。
(2) Exam1_:应用程序的主源文件。它将创建CExam1_1App类的一个对象,并覆盖InitInstance()成员函数。
(3) ,MainFrm.h:这两个文件将从CFrameWnd(SDI应用程序)或CMDIFrame(MDI应用程序)派生CMainFrame类。如果在AppWizard的Application Options对话框(6步中的第4步)中选择了对应的可选项,则CMainFrame类中将处理工具条和状态栏的创建工作。
(4) Exam1_,Exam1_1Doc.h:从CDocument类派生,并实现CExam1_1Doc文档类。
(5) Exam1_,Exam1_1View.h:派生并实现名为CExam1_1View的视图类。
3. 资源文件
AppWizard创建的与资源相关的文件:Exam1_1RC,RESOURCE.H,Exam1_2。资源文件含有:
(1) 一般MFC应用程序的默认菜单定义、加速键表和字符串表。
(2) 指定了默认的About对话框和一个图标文件(RESExam1_)。
23 VC++6简明教程
(3) 包含标准的MFC类的资源。如果指定了支持工具栏,它还将指定工具栏位图文件()。
Exam1_2用于存放Visual Studio不可直接进行编辑的资源。
4. 预编译头文件
、StdAfx.h用于建立一个预编译的头文件Exam1_和一个预定义的类型文件。
MFC体系结构非常大,包含许多头文件,如果每次都编译,很费时。因此,常用的MFC头文件如afxwin.h、afxext.h、afxdisp.h、afxcmn.h等,都放在StdAfx.h中,然后让包含该StdAfx.h文件。由于编译器可识别哪些文件已编译过,所以只需编译一次,并生成预编译头文件,用于存放头文件编译后的信息。采用预编译头文件可以加速编译过程。
3.3 消息映射
在W32的窗口函数中,采用switch-case结构进行消息处理。而在MFC应用程序中,采用消息映射的方法,将消息映射到各个消息处理函数。
3.3.1消息的类别及其描述
MSG结构用于描述消息,区别消息一般方法是:对结构中的主消息message、附加参数wParam和lParam 这3个字段进行判断。
在MFC应用程序中,消息分为窗口消息、命令消息和控件消息三种类型。
1. 窗口消息
系统可以产生窗口消息,与窗口交互也能产生窗口消息。窗口消息只能被窗口或者窗口对象处理。在MFC应用程序中,CView和CFrame及其派生类、及自定义窗口类能够处理窗口消息。
窗口消息的字段格式为:
(1) message:WM_XXX
(2) wParam和 lParam:随WM_XXX而变
例如:
WM_ CLOSE :关闭窗口时产生,附加信息wParam和 lParam均未用。
WM_CREATE:窗口创建消息,由CreateWindow发出,wParam未用,lParam包含一个指向CREATESTRUCT的指针。
WM_LBUTTONDOWN:鼠标左键消息,wParam是一个整数值,标识鼠标键按下的状态,是左键、右键还是中键。lParam的低字节是当前光标的X坐标,高字节是Y坐标。
所以,要关闭一个窗口,只需要发给它消息包(WM_CLOSE,0L,0L)即可。
2. 命令消息
选择菜单项、单击工具按钮、按加速键及程序中的命令等都可以产生命令消息。在MFC应用程序中,凡是从基类CCmdTarget派生的类都能处理命令消息。
命令消息的字段格式如下:
(1) message :WM_COMMAND
(2) wParam:低16位为命令ID、高16位为0
24 (3) lParam:0L
3. 控件消息
当控件事件发生时,如改变文本框控件的内容、选择列表框控件中的某一选项等,都会产生控件消息。
控件消息的字段格式如下:
(1) message:WM_NOTIFY
(2) wParam:控件ID
(3) lParam:指向NMHDR的指针,NMHDR是包含了消息内容的一个结构
在VC中,利用前缀符号,可以识别不同的消息类别,如表3-5所示。
表3-5 系统定义的消息宏前缀
前缀
BM
CB
DM
EM
LB
SBM
WM
按钮控制消息
组合框控制消息
默认下压式按钮控制消息
编辑控制消息
列表框控制消息
滚动条控制消息
窗口消息
消息分类
3.3.2 消息映射系统
MFC的消息映射系统是由两大块构成:一块是CCmdTarget的派生类;另一块是消息映射。由CCmdTarget派生的子类都能够接收和处理消息,在每一个子类中都定义一个消息映射表,保存该类能够接受并处理的消息与及其消息处理函数的信息,同时消息处理函数定义为该类的成员函数。例如,一个典型的文档视图结构的MFC应用程序中,应用程序类、窗口框架类、文档类、视图类中都定义了一张消息映射表,维护着在本类中可以进行的消息处理。窗口程序按一定的路径搜索这些类的消息映射表,就能够找到并调用消息处理函数响应消息。
MFC提供了三个宏管理消息映射,它们是DECLARE_MESSAGE_MAP(),BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()。
在每一个CCmdTarget的派生类的定义中,都包含DECLARE_MESSAGE_MAP(),用于声明一个消息映射表的构成。在该类的实现文件中都包含BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP(),构成一张消息映射表,前者标志着消息映射表的开始,后者标志着消息映射表的结束。例如应用程序Exam1_1,在应用程序类的实现文件Exam1_中有一消息映射表,如程序清单3-7所示。
程序清单3-7:Exam1_中的消息映射代码
BEGIN_MESSAGE_MAP(CExam1_1App, CWinApp)
//{{AFX_MSG_MAP(CExam1_1App)
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!
25 VC++6简明教程
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
在两个宏之间放置着若干条消息映射记录。一条消息映射记录由消息宏与参数组成,例如消息映射记录ON_COMMAND(ID_FILE_NEW,CWinApp::OnFileNew)处理菜单项File->New 。处理命令消息的宏是ON_COMMAND,它需要两个参数,一个是菜单消息ID,另一个是菜单消息句柄,也就是消息处理函数。不同类型的消息,消息宏和参数是不同的,如表3-6所示。
表3-6 消息处理宏格式
消息类型
预定义窗口消息
命令消息
更新命令消息
控件消息
用户自定义消息
宏格式
ON_WM_XXX
ON_COMMAND
ON_UPDATE_COMMAND_UI
ON_XXX
ON_MESSAGE
无
命令ID,消息处理函数名
命令ID,消息处理函数名
控件ID,消息处理函数名
自定义消息ID,消息处理函数名
参数
开发MFC应用程序时,程序员可以使用ClassWizard,在Message Maps标签中方便地创建,删除,编辑一个消息映射。下面例3-3演示了使用ClassWizard为视图类增加一个窗口消息映射的过程。
【例3-3】创建一个单文档的MFC应用程序Exam3_3,并实现功能:当在视图中使用鼠标右键双击时,弹出对话框,显示鼠标的坐标。
1. 使用AppWizard创建一个单文档的MFC应用程序Exam3_3;
2. 添加一个鼠标双击的消息映射
(1) 打开ClassWizard,选择Message Map标签。
(2) 如图3-11所示,在Class name列表中选择CExam3_3View,Object IDs列表中选择CExam3_3View,Messages列表中选择WM_RBUTTONDBLCLK,单击“Add Function”按钮,完成鼠标右键双击消息映射,消息处理函数作为CExam3_3View的成员函数,名为OnRButtonDblClk。
26
图3-11 使用ClassWizard增加消息映射
3. 添加消息处理函数代码,实现功能
(1) 双击Member functions列表中添加的消息映射条目或者单击“Edit Code”按钮直接进入消息处理函数的文本编辑环境。
(2) 添加程序清单3-8所示粗体部分代码。
程序清单3-8:实现鼠标双击的消息处理函数
void CExam3_3View::OnRButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CString str;
("now:%d,%d",point.x,point.y );
MessageBox(str);
CView::OnRButtonDblClk(nFlags, point);
}
增加一个消息映射,ClassWizard自动完成的工作包括:
(1) 在消息映射所属类的头文件Exam3_3View.h中增加了消息处理函数的声明如下:
//{{AFX_MSG(CExam3_3View)
afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
//}}AFX_MSG
前缀afx_msg表示该成员函数是一个消息处理函数
(2) 在该类的实现文件Exam3_的消息映射表中增加消息映射记录如下:
//{{AFX_MSG_MAP(CExam3_3View)
ON_WM_RBUTTONDBLCLK()
//}}AFX_MSG_MAP
27 VC++6简明教程
在消息映射表中,“//{{AFX_MSG_MAP(CExam3_3View)” 和“//}}AFX_MSG_MAP”之间的消息映射记录,呈灰色显示,表示这些记录由ClassWizard来维护的。
(3) 在该类的实现文件中添加一个空的消息处理函数的定义
要使用ClassWizard删除一个消息映射,需要分为两步:
(1) 在Message Maps标签中Member Functions列表中选择需要删除的消息映射处理函数,单击“Delete Function”按钮。此时ClassWizard会将类的头文件中对消息处理函数的声明,及实现文件的消息映射表中的消息映射记录删除。
(2) 手工删除实现文件中消息映射函数的定义。
ClassWizard不会自动删除消息映射函数的定义,因为有可能程序员已经在其中添加了自定义的功能代码,所以这部分需要程序员自己删除。
3.3.3消息处理的路径
从前面的讲述,可以看到MFC应用程序的消息处理机制与Win32应用程序有很大的不同,它使用基本类的消息映射表代替了Win32的窗口函数WndProc中的switch-case结构,使消息映射系统的结构更为简洁明了,但是消息的处理路径却更为隐蔽。
MFC应用程序的消息循环是由应用程序类的成员函数Run()来完成的,Run()函数,调用成员函数GetMessage(),TranslateMessage()和DispatchMessage()维护一个消息循环。接收到一个消息后,DispatchMessage()函数会把消息交给全局函数AfxWndProc()函数,经过一系列的处理,最后将消息传递给窗口类的OnWndMsg(),OnWndMsg()函数负责将收到的消息分为三大类:窗口消息、命令消息和控件消息,再分发给不同的消息处理函数去处理。
1. 窗口消息的处理
OnWndMsg()函数直接处理窗口消息,窗口消息的处理只能由窗口类及其派生类完成,从MFC类的继承关系中可以知道只有窗口框架类和视图类及其基类是能够接收和处理窗口消息。OnWndMsg()函数处理窗口消息的过程通常为:
(1) OnWndMsg()直接搜索窗口消息所属的窗口类的消息映射表,如果找到了匹配的消息处理函数,就执行消息处理函数;
(2) 如果找不到,继续搜索该窗口类的基类,如果找到了匹配的消息处理函数,就执行消息处理函数;
(3) 如果还没有找到,则把消息交给默认窗口函数DefWindowProc()处理。
2. 命令消息的处理
由于从CCmdTarget派生的类均可处理命令消息,所以命令消息的处理路径比窗口消息处理要复杂得多。MFC应用程序框架按特定顺序处理命令消息,如图3-12所示。
视图类 文档类
文档模板类 框架窗口类
应用程序类
图3-12 命令消息处理顺序
OnWndMsg()函数会将命令消息分发给窗口类的OnCommand()函数,OnCommand()函数调用成员函数OnCmdMsg()函数,该函数会按照命令消息处理的顺序,依次搜索视图类、文档类、文档模板类、框架窗口类和应用程序类及其基类中的消息映射表,判别该类是否定义了该命令消息的处理函数,一旦搜索到消息处理函数,立即停止命令传递,执行消息处理函数。如果全部查找完毕,依然不能处理,则该28 命令消息所对应的界面元素变灰。如:工具栏图标或菜单项变灰,表示该命令不起作用。
例如,在一个应用程序中有一个菜单命令Version(ID为ID_VIEW_VERSION),在视图类为该菜单命令消息映射了处理函数CXXXView::OnViewVersion(),同时在窗口框架类也为该菜单命令消息映射了处理函数CMainFrame::OnViewVersion()。在程序运行时,当执行菜单命令Version时,按照图3-12所示的处理顺序,菜单消息应由CXXXView::OnViewVersion()函数响应,窗口框架类中定义的消息处理函数将没有机会被执行。
3. 控件消息的处理
OnWndMsg()函数将控件消息分发给窗口类的OnNotify()函数。OnNotify()函数处理控件消息过程为:
(1) 把消息交给控件所属的类,如果能够处理,执行消息处理函数;
(2) 如果控件所属的类不能处理,调用控件的父窗口对应类的OnCmdMsg(),搜索父窗口对应类的消息映射表,以获得处理该消息的函数。
3.3.4自定义消息处理
Windows系统已经定义了很多窗口消息,如:鼠标消息、键盘消息、窗口创建消息、窗口关闭消息等等,同时系统还允许用户定义自己的窗口消息。自定义窗口消息有两种方法:一是指定自定义窗口消息对应的整数值;二是自定义相应窗口消息的字符串名称。
每个窗口消息都固定地对应着一个整数值,系统定义的窗口消息保留从0到WM_USER整数值,用户自定义窗口消息的映射范围在WM_USER+1到0x7fff之间。
1. 指定整数值的自定义窗口消息
使用整数值定义一个自定义的窗口消息的步骤如下:
(1) 在一个窗口类的头文件中定义一个自定义的窗口消息
例如,在应用程序Exam3_3视图类的头文件中定义一个窗口消息WM_EXAMPLE,定义语句如下:
#define WM_EXAMPLE WM_USER+5
(2) 在类中声明该消息的处理函数
例如,在视图类的类定义(头文件中)的保护段中增加一个消息处理函数OnExample的声明,语句如下所示:
afx_msg LRESULT OnExample(WPARAM,LPARAM);
(3) 在类的消息映射表中加入映射项
例如,在视图类的实现文件的消息映射表中,添加一条WM_EXAMPLE消息处理记录,指明使用消息处理函数OnExample处理该消息。用户自定义消息宏为ON_MESSAGE,如下所示:
ON_MESSAGE(WM_EXAMPLE,OnExample)
(4) 在类的实现文件中实现该消息的处理函数
例如,OnExample函数的功能是响应一个消息对话框,程序代码如下:
程序清单3-9:WM_EXAMPLE的消息处理函数
LRESULT CExam3_3View::OnExample(WPARAM wParam,LPARAM lParam)
{
AfxMessageBox(“This is a user defined message!”);
return 1;
}
29 VC++6简明教程
(5) 窗口指针调用SenMessage函数或是PostMessage函数给窗口发消息,使窗口类能处理该消息
例如:为了获得视图类指针,可以在“查看”菜单中定义一个菜单命令“发送消息”,ID为ID_VIEW_SNDMSG,并在视图类为菜单命令映射消息处理函数。这样可以通过this指针获得视图指针。在消息处理函数中添加代码发送WM_EXAMPLE消息,如程序清单3-10所示。
程序清单3-10:使用视图指针发送WM_EXAMPLE消息
void CExam3_3View::OnViewSndmsg()
{
// TODO: Add your command handler code here
this->SendMessage(WM_EXAMPLE,0,0);
}
运行应用程序,执行菜单命令“发送消息”,就会弹出消息对话框显示文本串“This is a user defined
message!”。如图3-13所示。
图3-13 自定义窗口消息运行结果
2. 指定字符串的自定义窗口消息
使用字符串定义一个自定义的窗口消息,也就是指由用户为消息指定一个名称,由系统自动分配窗口消息的整数值,步骤大致与指定整数值相同,可以对方法一的示例进行改写:
(1) 定义并注册该消息
例如,在视图类的头文件中为自定义消息指定字符串为“MESSAGE_TAG”,如下所示:
#define MESSAGE_TAG “HERE-IS-A-USER-DEFINED-MESSAGE”
UINT WM_EXAMPLE =::RegisterWndMessage(MESSAGE_TAG);
(2) 在类的消息映射表中加入映射项
此时消息宏为ON_REGISTERED_MESSAGE,语句如下所示:
ON_REGISTERED_MESSAGE(WM_EXAMPLE,OnExample)
其它步骤都与指定整数值的自定义窗口消息的使用方法相同。
30 实验
实验1:手工编写MFC应用程序
在这个实验中,你将不使用应用程序向导,手工编写一个最小的MFC应用程序。
1. 创建一个新工程
(1) 在集成开发平台,打开File菜单,单击New菜单项。
(2) 在New对话框中,选择Projects标签,再作以下操作:工程类型选择“Win32 Application”;工程名称键入“Ex3_1”;设置工程存储位置;接受默认平台Win32;创建新项目工作区,单击“OK”按钮。
(3) 选择“An empty project”单选钮,单击“Finish”按钮,显示New Project Information对话框,单击“OK”按钮,回到集成开发平台。完成Win32工程的创建。
2. 增加一个头文件到工程
(1) 在集成开发平台,打开Project菜单,指向Add To Project,单击New菜单项。
(2) 在New对话框中,选择Files标签,再作以下操作:文件类型选择“C/C++ Header File”;选中“Add to Project“复选框;文件名称键入“MinApp”。
(3) 创建新文件,单击“OK”。
3. 编辑头文件,从基类CWinApp和CFrameWnd继承子类
(1) 包含 Afxwin.h头文件;
#include
(2) 从CWinApp公有继承CMyApp类,在公有段重载成员函数InitInstance();
(3) 从CFrameWnd公有继承CMainFrame类;
(4) 保存头文件MinApp.h,文件内容如程序清单3-11所示。
程序清单3-11:MinApp.h文件
#include
class CMyApp:public CWinApp
{
public :
virtual BOOL InitInstance();
};
class CMainFrame: public CFrameWnd
{
};
4. 增加一个实现文件到工程
(1) 集成开发平台,打开Project菜单,指向Add To Project,单击New菜单项。
(2) New对话框中,选择Files标签,再作以下操作:文件类型选择“C/C++ Source File”;选中“Add
to Project“复选框;文件名称键入“MinApp”。
(3) 创建新文件,单击“OK”。
31 VC++6简明教程
5. 编辑实现文件,为成员函数编写代码
(1) 包含MinApp.h 头文件;
#include “MinApp.h”
(2) 声明下面变量;
CMyApp myApp;
(3) 为CMyApp实现成员函数InitInstance,实现代码如程序清单3-12所示。
程序清单3-12:文件
#include “MinApp.h”
CMyApp myApp;
BOOL CMyApp::InitInstance()
{ m_pMainWnd=new CMainFrame;
((CMainFrame*)m_pMainWnd)->Create(NULL,”The MFC Application”);
m_pMainWnd->ShowWindow(m_nCmdShow);
return TRUE;
}
6. 修改工程设置
(1) 集成开发平台,打开Project菜单,选择Setting菜单项,再作以下操作:在Setting for下拉列表中选择“Win32 Debug”;选择General属性页,在Microsoft Foundation Classes下拉列表中选择“Use MFC in a Static Library ”。这将会把MFC库中的二进制信息直接绑定到你的程序。
(2) 单击“OK”。
7. Build并运行当前工程
(1) 集成开发平台,打开Build菜单,选择Build Ex3_菜单项,编译应用程序。
(2) 打开“Build”菜单,选择Execute Ex3_菜单项,运行应用程序。
程序运行效果如图3-14所示,一个最简单的窗口程序,标题为“The MFC Application”。
图3-14 应用程序Ex3_1运行效果图
实验2:基于对话框的MFC应用程序
在这个实验中,你将学习使用应用程序向导,创建一个基于对话框的MFC应用程序,并编译执行这个程序。
1、创建一个新工程
32 (1) 在集成开发平台,选择File->New菜单命令,打开New对话框。
(2) 在New对话框中,选择Project标签,单击MFC Application(EXE)。
(3) 在右边填写工程名称:Ex3_2,并确定工程文件保存位置。
(4) 单击“OK”按钮,在MFC AppWizard-Step 1对话框中,选择应用程序的类型为Dialog based。.
(5) 单击“Finish”按钮,显示New Project Information对话框,单击“OK”按钮,回到集成开发平台。
2、执行并修改程序
(1) 编译并运行该应用程序。
(2) 在工作区窗口中选择ResourceView面板,展开Dialog文件夹,双击IDD_ Ex3_2_DIALOG打开对话框资源编辑窗口。
(3) 右击对话框窗口中间的文本对象,弹出快捷菜单,选择Properites菜单项,在打开的Text Properites对话框中修改Caption内容为:“你已开始了VC++应用程序的学习!”。关闭此属性窗口。
(4) 右击对话框窗口任意空白的的位置,弹出快捷菜单,选择Properites菜单项,在打开的Dialog
Properites对话框中单击“Font”按钮,修改字体为“三号”。
(5) 关闭此属性窗口,调整对话框的大小,重新运行应用程序。
程序运行效果如图3-15所示,一个最简单的基于对话框的MFC应用程序。
图3-15 应用程序Ex3_2运行效果图
实验3:文档/视图单文档MFC应用程序的构成
在这个实验中,你将学习文档/视图应用程序的基本构成。
1. 创建一个MFC的文档/视图单文档应用程序Ex3_3
2. 增加一个鼠标消息,并响应消息框
功能:当单击鼠标右键时,弹出消息框,显示“right mouse key”。
步骤:
(1) 选择菜单命令View->ClassWizard,打开ClassWizard窗口, 选择 Message Maps标签。
(2) Class name列表中选择视图类CEx3_3View,Object IDs列表中选择视图类CEx3_3View,Messages列表中选择窗口鼠标单击右键消息WM_RBUTTONDOWN。
(3) 单击“Add Function”增加消息映射,生成新的成员函数:OnRButtonDown()。
(4) 单击“Edit code”,在OnRButtonDown()函数中注释提示添加代码的位置输入语句:
AfxMessageBox(“right mouse key”);
(5) 运行程序,测试功能。
3. 在View中增加文本输出
33 VC++6简明教程
功能:在视图的中央输出文本“my first single document ”。
步骤:
(1) 在工作区中选择ClassView面板,展开CEx3_3View类。
(2) 在CEx3_3View下找到成员函数OnDraw(),双击打开该函数编辑窗口,在注释提示添加代码的位置添加以下代码:
pDC->TextOut(250,150,” my first single document”);
(3) 运行程序,测试功能。
4. 鼠标消息响应文本输出
功能:当单击鼠标右键时,在鼠标点击的位置上显示文本“right click ”。
步骤:
(1) 按程序清单3-13中粗体部分代码所示,修改第一个小实验中生成的CEx3_3View::
OnRButtonDown()函数
(2) 运行程序,使用鼠标右键单击视图,观察运行结果。
程序清单3-13:CTestView:: OnRButtonDown()程序代码
void CEx3_3View::OnRButtonDown(UINT nFlags, CPoint point)
{ //定义客户区设备上下文对象,并用this指针将该对象指向view
CClientDC dc(this);
t( point.x,point.y,"right click");
//point 给出了鼠标的位置[point.x,point.y]
CView::OnRButtonDown(nFlags, point);
}
5. 增加一个自定义菜单项,响应菜单项消息框
功能:在菜单“查看”下增加一个菜单项“版本”,当点击菜单项,弹出消息窗口给出版本信息:“CourseWare test ver1.0”,并设置加速键“Alt+F12”。
步骤:
(1) 在项目工作区窗口中选择ResourceView面板,展开Menu文件夹,双击IDD_MAINFRAME,打开菜单资源编辑器。
(2) 在“查看”菜单下双击空白条,打开Menu Item Properties窗口, ID自定义为:ID_VIEW_VERSION,Caption输入为:版本tAlt+F12。增加了菜单项“版本”。
(3) 在ResourceView面板,展开Accelerator文件夹,双击IDD_MAINFRAME打开加速键资源编辑器。
(4) 在加速键资源编辑器窗口中双击最下面的空白条,打开Accel Properties窗口,先输入ID为
ID_VIEW_VERSION,单击“Next Key Typed”按钮,并按键盘上的“F12”键,再在右边选择“Alt”复选钮。为菜单编辑了加速键Alt+F12。
(5) 选择菜单命令View->ClassWizard,打开ClassWizard,选择Message Maps标签。
(6) Class name列表中选择框架类CMainFrame,Object IDs列表中选择自定义菜单项ID_VIEW_VERSION,Messages列表中选择COMMAND。
(7) 单击“Add Function”增加消息映射,生成新的成员函数:OnViewVersion(),并单击“Edit Code”,在成员函数注释提示添加代码的位置增加代码如下:
AfxMessageBox(“CourseWare test ver1.0”);
(8) 编译运行程序,执行菜单命令“查看”->“版本”,测试功能。
6. 定义Document的存储变量(CString ,int ),并输出Document的内容
34 功能:为文档对象增加数据成员recno(int类型),表示学号;stuname(CString类型)表示学生姓名,并在视图中输出文档对象中的内容。
步骤:
(1) 在工作区的ClassView面板中,鼠标右键单击CEx3_3Doc类,弹出快捷菜单,选择Add Member
Variable菜单项。
(2) 在弹出的Add Member Variable对话框中填写和选择:
Variable Type:int
Variable Name:recno
Access:选择Public
按“OK”创建第一个公有数据成员recno。
(3) 同样的方法创建第二个公有数据成员stuname。
(4) 找到CEx3_3Doc类的OnNewDocument()函数,在其中注释提示添加代码的位置增加以下代码为数据成员赋值:
recno=0;
stuname=“noname”;
(5) 编辑CEx3_3View的OnDraw()函数,修改代码如程序清单3-14粗体部分所示,实现在视图窗口坐标[250,200]的位置输出学生信息。
程序清单3-14:CEx3_3View的OnDraw函数代码
void CEx3_3View::OnDraw(CDC* pDC)
{
CEx3_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CString stuinfo;
("%d::%s",pDoc->recno,pDoc->stuname);
pDC->TextOut(250,200,stuinfo);
}
(6) 编译运行程序,测试功能。
7. 通过按键改变文档对象的值
功能:当按上箭头键时,能够使学号每次递增1,并在视图上反映学号的变化。
步骤:
(1) 打开ClassWizard窗口,选择Message Maps标签。
(2) Class name列表中选择视图类CEx3_3View,Object IDs列表中选择CEx3_3View,Messages列表中选择键盘按下键消息WM_KEYDOWN。
(3) 单击“Add Function”增加消息映射,生成新的成员函数:OnKeyDown(),并单击“Edit Code”,在成员函数中增加代码如程序清单3-15粗体部分所示。
程序清单3-15:CEx3_3View的键盘按下键消息处理函数
void CEx3_3View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
CEx3_3Doc* pDoc = GetDocument();
35 VC++6简明教程
if (nChar==VK_UP)//VK_UP是上箭头键的虚拟码
{ pDoc->recno++;//学号增1
Invalidate();//更新视图
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
(4) 编译运行程序,测试功能。
8. 文件保存文档对象的内容
功能:能保存文档对象的内容到一个磁盘文件中,并能从磁盘文件中取出内容到文档对象。
步骤:
(1) 在文档类CEx3_3Doc中,找到文档类的成员函数CEx3_3Doc::Serialize(),修改代码如程序清单3-16所示的粗体部分。
程序清单3-16:CEx3_3View:: Serialize函数代码
void CEx3_3Doc::Serialize(CArchive& ar)
{
if (ing())
{
// TODO: add storing code here
ar< } else { // TODO: add loading code here ar>>recno>>stuname; } } (2) 重新编译运行程序,通过单击上箭头键修改学号的值到15,执行菜单命令“文件”->“保存”,文件取名为:stu1。 (3) 执行菜单命令“文件”->“新建”,可以看到学号重新归零。 (4) 执行菜单命令“文件”->“打开”,选择文件stu1,此时显示学号为15,说明文件保存成功。运行效果如图3-16所示。 图3-16应用程序Ex3_3运行效果图 36 实验4:(独立练习) 创建一个MFC单文档应用程序,编程实现以下功能: (1) 在视图中点击鼠标左键,输出点击处的坐标。 (2) 在文档对象中使用一个数组记录所有鼠标左键点击的坐标位置。 (3) 保存当前视图到文件。 自测题 1、 是窗口应用程序运行的核心工作机制,消息往往用一个的结构体 来表示。 2、Windows操作系统下的窗口应用程序采用 的程序设计,与DOS操作系统下的 的程序设计有着明显的不同。 3、所有的窗口应用程序必须包含两个基本函数 和 。 4、 类和 类是MFC应用程序最基本的两个类,所有的MFC应用程序都必须包含这两个类。 5、在MFC应用程序中,消息分为 消息、 消息和 消息三种类型。 6、为什么不能在文档类定义一个键盘消息的消息处理函数,而通常在视图类中定义。 7、如果为同一个菜单命令分别在视图类和文档类中映射了两个消息处理函数,在视图类中的消息处理函数在视图中输出一行文本串,在文档类的消息处理函数是弹出一个消息对话框。请描述程序运行时,执行这个菜单命令的结果。 8、在文档类的成员函数中和视图类的成员函数中都能够修改文档对象的数据,但若要更新视图对文档对象数据的显示,两者的处理有何不同? 小结 1. Windows操作系统下的窗口应用程序采用事件驱动的程序设计,事件驱动的程序的逻辑顺序由按事件的产生而决定的,事件的产生不是预先定义的,有着随机性。 2. 消息结构体MSG定义: typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; }MSG; 其中:POINT是结构体类型,表明屏幕光标的位置,其定义: typedef struct tagPOINT { 37 VC++6简明教程 LONG x; LONG y; } POINT; 3. 使用VC++开发窗口应用程序可分为三大类:一是Win32应用程序;二是MFC应用程序;三是ATL应用程序。 4. 所有的窗口应用程序必须包含两个基本函数: (1) 应用程序主函数WinMain(),负责: 注册窗口类:首先使用数据结构WNDCLASS定义窗口的各个属性,再使用Windows API函数RegisterClass()注册窗口类; 创建并初始化窗口:使用CreateWindow()创建、使用ShowWindow ()和UpdateWindow ()显示更新窗口; 创建消息循环:使用while循环语句,GetMessage()取出消息,TranslateMessage()将消息翻译为WM_XXX形式的命令,DispatchMessage()将消息分发给相应的窗口函数。 (2) 窗口处理函数WinProc():处理所有从操作系统传递到窗口的消息。DefWindowProc()为未定义处理过程的消息提供缺省处理。PostQuitMessage()向应用程序发出WM_QUIT消息,请求退出处理函数。 5. CObject是MFC类库的根类。 6. MFC应用程序类必须包含两个基本类:CWinApp类和CFrameWnd类。 (1) CWinApp:替代了主函数WinMain()的功能,封装了与应用程序相关的程序启动InitApplicaiton()和InitInstance()、消息循环启动Run()和程序结束ExitInstance()等功能。 (2) CFrameWnd:替代了窗口函数WndProc()的功能,封装了窗口创建、消息处理和窗口销毁等功能。 7. 文档类对象负责存储、加载和保存数据及数据初始化,声明了两个虚函数: (1) OnNewDocument():可对文档初始化。 (2) Serialize():ar<<保存;ar>>读取。 8. 视图类显示存储在文档类对象中的数据,并允许用户修改这些数据。其中成员函数: (1) CXXXDoc* GetDocument():指向文档类的指针。 (2) OnDraw(CDC* pDC):向视图窗口输出。 9. 文档模板类的主要作用是: (1) 保存了文档、视图和框架窗口等类的CRuntimeClass对象的指针。 (2) 包含了该文件类型用到的资源标识符,如菜单、图标、快捷键等资源。 (3) 包含了关于该文件的额外信息,如:文件类型名称、后缀等字符串及其他的用户接口信息。 (4) 单文档模板CSingleDocTemplate、多文档模板CMultiDocTemplate。 10. 文档类与视图类的通信:GetDocument()和UpdateAllViews()。 11. MFC的启动顺序: (1) 建立、初始化 CWinApp对象,该对象是全局的且只能有一个,名为theApp。 (2) 在InitInstance()函数中:创建文档模板、执行MFC框架默认的命令行参数、根据分解的命令行信息,并启动不同类型任务、动态建立文档、视图、框架,并对文档、视图、框架进行初始化。 (3) 显示与更新窗口。 (4) 启动消息循环。 12. 在MFC应用程序中,消息分为窗口消息、命令消息和控件消息三种类型。 13. MFC消息映射的原理是:在每个能接收和处理消息的类中,定义一个消息及其处理函数的静态对照表,称为消息映射表。程序通过搜索消息映射表,就可判断该类能否处理此消息,并迅速找到并调用相应的消息处理函数。 14. 消息处理的路径:应用程序从CWinApp派生对象,其成员函数Run()调用CWinThread::Run(),通过38 GetMessage(),TranslateMessage()和DispatchMessage()进行消息循环。每个窗口对象都使用相同的称为AfxWndProc()的全局函数,AfxWndProc()调用OnWndMsg()处理消息。MFC应用程序对消息是分类处理的。 15. Windows系统定义的窗口消息,固定地对应着从0到WM_USER之间的某个整数值,允许用户自定义窗口消息的映射范围在WM_USER+1到0x7fff之间。 16. 自定义窗口消息有两种方法:一是指定自定义窗口消息对应的整数值;二是定义相应窗口消息的字符串名称。 17. 39
发布者:admin,转转请注明出处:http://www.yc00.com/web/1689605232a269948.html
评论列表(0条)