版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
文档与视图应用程序第04章主要内容4.2文档与视图结构4.3文档与视图类4.4特殊视图类4.5文档与视图应用4.1消息映射机制4.5文档与视图应用4.2文档与视图结构4.1消息映射机制4.1.1消息的种类窗口消息命令消息控件通知消息1.窗口消息窗口消息(WindowsMessage)一般与窗口的内部运行有关,如创建窗口、绘制窗口、销毁窗口等。通常,消息是从系统发送到窗口,或从窗口发送到系统。当用SendMessage()或PostMessage()发送一个窗口消息时,变量Message、wParam、lParam的格式如下:MessagewParam
lParam
WM_XXX
定义的命令
定义的命令
1.窗口消息WM_XXX可以是许多窗口消息之一,如下列窗口消息:●WM_CREATE:告知窗口初始化自已●WM_PAINT:告知窗口绘制自己●WM_MOUSEMOVE:告知窗口鼠标在其上移动有关其它内容,请参考MSDN联机帮助。2.命令消息命令消息通常与处理用户请求有关,当用户单击一个菜单项或工具栏按钮时,命令消息就产生了。并发送到能处理该请求的类对象(如装载文件、编辑文本、保存菜单等)。当用SendMessage()或PostMessage()发送命令消息时,变量Message、wParam、lParam的格式如下:MessagewParam
lParam
WM_COMMAND0或CommandID
02.命令消息CommandID要么是选中的菜单项的ID,要么是被单击的工具栏按钮ID。注意CommandID不能大于一个字长,如果使它大于一个字长,系统就只用0来填写高位字。某些控件通知也用WM_COMMAND消息,区别两种消息唯一方法是lParam是否为NULL。3.控件通知消息通常,控件通知消息是指有重要的事情发生时,由控件窗口发送到父窗口的一种消息。控件通知父窗口为进一步控制子窗口提供了机会。例如,打开一个组合框,父窗口可以用组合框开始创建时无法得到的消息操控它。控件通知经历了一个演变过程。因而SendMessage()的变量Message、lParam、wParam有三种格式如下:3.控件通知消息(1)第一种控件通知消息格式第一种控件通知消息格式只能是窗口消息的子集。MessagewParam
lParam
WM_XXX
定义的命令
定义的命令
3.控件通知消息WM_XXX可以是以下消息中的任何一种:●WM_PARENTNOTIFY表示一个控件窗口要么已被建立或销毁,要么鼠标已单击了该窗口。●WM_CTLCOLOR、WM_DRAWITEM、WM_MEASUREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKEYTOITEM或WM_COMPAREITEM都是发往父窗口的消息,用来绘制自身的控件窗口。●WM_HSCROLL或WM_VSCROLL由滚动条发送,通知滚动窗口父窗口。3.控件通知消息第二种控件通知消息格式使用WM_COMMAND消息,与命令消息共享。MessageWParam
lParam
WM_COMMANDXN_XXX或控件ID窗口句柄3.控件通知消息●lParam变量用来区分是命令消息还是控件通知消息,控件通知消息在lParam有一个定义的句柄,用来标识发出通知的控件,而命令消息中的lParam
为NULL。●XN_XXX的值因发出通知控件的不同而不同,例如,当XN_XXX值为EN_CHANGE时,告知父窗口显示在编辑框中的文本已发生了变化。3.控件通知消息(3)第三种控件通知消息格式第三种控件通知消息格式也是最灵活的通知格式。它用WM_NOTIFY消息。MessagewParam
lParam
WM_NOTIFY控件ID指向NMHDR的指针3.控件通知消息
lParam的值指向一种结构,该结构可以包括有关该通知控件的任何内容,而不受空间和类型的限制,该结构叫做NMHDR。
typedef
struct
tagNMHDR{ HWNDhwnd; //WindowhandleofcontrolWiindow//makingthenotification UINTidFrom; //ControlIDofcontrolWindow//makingthenotificationUINTcode; //notificationcodeex:theuser//hasclickedtheControlWindow}NMHDR;NMHDR代表通知消息头(NotificationMessageHeader)。为什么要这个头?因为某些控件用NMHDR作为头发送一个更大结构的消息,这样那些不知道更大结构内容的函数还是能处理通知头。4.1.2消息传递与消息循环当用C语言来编写MS-DOS应用程序时,至少要有一个main()函数。当用户运行该应用程序时,操作系统会自动调用main()。从这一点讲,用户可以使用任何所需要的程序结构。如果程序需要得到用户的键盘输入,或者需要使用操作系统所提供的功能,那么它就可以调用适当的函数,如getchar()或者使用基于字符的函数库。
4.1.2消息传递与消息循环在Windows操作系统中运行程序时,它首先调用程序中的WinMain函数。因此,在Windows应用程序中一定要有WinMain函数,该函数一般用来完成某些特殊的任务,其中最重要的任务就是要创建该应用程序的“主窗口”。“主窗口”中必须包含用来处理Windows所发送的消息的代码。基于Windows程序和基于MS-DOS程序之间的一个最重要的差别就在于MS-DOS程序是通过调用操作系统的功能来获得用户输入的,而Windows程序则是通过操作系统发送的消息来处理用户输入的。提示:许多Windows开发环境,包括使用MicrosoftMFC类库的MicrosoftVisualC++,都是通过隐藏WinMain函数及构造消息控制机制来简化程序编写。虽然使用MFC类库编程不再需要WinMain函数,但是弄清操作系统和程序之间的这种关系是非常重要的。4.1.2消息传递与消息循环许多Windows消息都经过了严格的定义,并且适用于所有的程序。例如,当窗口被创建时系统就会发送WM_CREATE消息,当用户按下鼠标的左键时系统就会发送WM_LBUTTONDOWN消息,当用户敲了一个字符键时系统就会发送WM_CHAR消息,而当用户关闭窗口时系统又会发送WM_CLOSE消息。所有的消息都有两个32位的参数,可以保存如光标位置、键盘码等信息。当用户进行菜单选择和对话框按钮单击等操作时,系统又会相应地发送WM_COMMAND(命令消息)消息给当前窗口。命令消息的参数则完全依赖于窗口的菜单设计。用户还可以定义一些自已的消息,应用程序可以向桌面上的任何窗口发送这些消息,关于用户自定义消息的使用,读者可参阅相关MSDN在线帮助。4.1.2消息传递与消息循环读者完全不必担心如何使这些消息与代码关联,因为这是应用程序框架的职责。不过需要注意,Windows的这种消息处理机制同时也强加给了用户程序许多固定的结构。因此,不要强迫Widnows程序看起来像老式的MS-DOS程序,只要看过一些有关这方面的书籍,就不难发现这些程序的确与以往的程序有所不同。基于DOS的应用程序主要使用顺序的、过程驱动的程序设计方法。顺序的、过程驱动程序有一个明显的开始、过程和结束,因此,程序能直接控制程序事件或者过程顺序。基于Windows程序的执行顺序取决于事件发生的顺序,程序的执行顺序是由顺序产生的消息驱动的,但是消息的产生往往并不要求有次序之分。消息驱动程序设计是围绕着消息的产生与处理而展开,一条消息就是关于发生事件的消息,Windows程序员的工作就是对正在开发的应用程序所要发出或者接收的消息进行排序和管理,由于Windows应用程序是消息驱动的,消息是不会以任何预定义的顺序出现的。4.1.2消息传递与消息循环
MFC类库的优越性在于MFC类库比较完整地封装了WindowsAPI函数,MFC类库为经常使用WindowsAPI函数提供支持,包括窗口函数、消息、控件、菜单、对话框、GDI对象、对象连接以及多文档界面等。此外MFC类库也提供了具有共性的应用程序操作,如打印、打印预览、状态栏、工具栏、数据库支持和OLE支持等。MFC类库支持多线程,所有的应用至少有一个线程,这个线程由CWinApp类的对象使用,被称为“主”线程。为方便多线程编程,MFC类库还提供了同步对象等。MFC类库具有同以C语言为基础使用WindowsAPI开发的Windows应用程序共存的能力。在同一程序中,程序员可以同时使用MFC类和WindowsAPI调用。MFC类库提供了自动消息处理功能,MFC类库消除了最易产生错误的来源,即WindowsAPI的消息循环,MFC类库将自动处理每一个Windows消息,从而替代了使用的分支语句,且每一条Windows消息被直接映射到一个进行处理的成员函数。MFC类库使用与WindowsAPI相同的命名约定,这使得程序员根据类名就可以知道类的功能。1.Windows消息
Windows操作系统定义了900多个Windows消息,系统将用户输入传给应用程序,然后又为每个输入产生消息,此外,系统还为应用程序的变化产生消息,应用程序自己也可以产生消息,用于对应用程序内部发生的特殊事件进行处理或用于同其他应用程序进行通信。系统向应用程序发送消息时,将向应用程序发送4个参数:1.Windows消息(1)窗口的句柄:窗口句柄是用于标识窗口的常数,Windows中每个窗口都有一个窗口句柄。消息参数中的窗口句柄标识的是接受消息的窗口。(2)消息标识:标识所产生的消息。例如:WM_CREATE,其中,WM_代表WindowsMessage,是Windows窗口产生的消息,而WM_CREATE代表窗口正在被创建,WM_PAINT代表窗口的客户区需要被重绘。(3)两个32位参数:消息参数定义了Windows应用程序处理消息所需要的数据或者数据所在位置,这两个参数的含义与具体的消息有关。消息参数可以包含一个整数、一组标志或者一个结构对象指针;消息参数也可以是NULL,表示消息没有参数。应用程序通过消息的标识来确定当前消息将会有哪些参数,这些参数的意义和具体消息有关。2.Windows消息的取值
Windows消息实际上是由宏定义的整数,如WM_USER被定义为#defineWM_USER400,因此,要定义一个消息标识只需要为该消息标识分配一个无符号整型数即可。但是,该整数的取值并不是任意的,因为Windows将整数划分为如下几个范围,每个范围的整数适用于不同类型的消息:2.Windows消息的取值(1)范围1:从0x0000到0x03FF的整数被用于定义系统的消息,因此,这个范围的整数不能用于定义自定义消息。(2)范围2:从0x0400到0x7FFF的整数可被应用程序用于定义自定义消息,因此,我们的自定义消息的整数值必须定义在WM_USER到WM_USER+31743之间。(3)范围3:如果应用程序的版本号大于4.0,可将从0x8000到0xBFFF的整数用于自定义消息,即此时应用程序还可以将WM_APP至WM_APP+16383之间的整数用于定义自定义消息。2.Windows消息的取值(4)范围4:自定义消息与其他应用程序中的消息可能会相重复,此时将会发出冲突,因为同样的消息在两个应用程序中有不同含义,为了避免这种错误,可以使用RegisterWindowMessage()函数来获取一个在系统中全局唯一的消息标识,RegisterWindowMessage()函数的原型如下: UINTRegisterWindowMessage(LPCTSTR
lpString);
其中,lpString为一个字符串,用于说明消息。该函数的返回值是一个消息的标识,是一个整数,该整数的范围在0xC000到0xFFFF之间。如果几个应用程序使用RegisterWindowMessage()函数定义了同样的字符串,则他们可以通过RegisterWindowMessage()函数返回相同的整数,共享同样的消息。(5)范围5:大于0xFFFF的整数,该范围的整数被Windows系统保留,用于今后对Windows消息进行扩充。3.Windows消息的行踪Windows消息产生后,将由系统向应用程序发送,按照消息发送的方式,Windows消息发送分为使用队列的发送方式和不使用队列的发送方式。(1)用队列的发送方式
使用队列发送方式进行消息传送时,消息被存储于一个先进先出(FIFO)的队列中。这类消息发送方式主要用于处理用户的键盘、鼠标输入以及定时器、绘制和退出等消息。Windows系统中总是存在一个系统消息队列,而对于用户界面线程又有一个线程消息队列。对非用户界面线程,Windows系统在该线程启动时将不为它分配消息队列,直到调用了Windows用户函数或GDI函数为止。当用户通过键盘或鼠标进行输入后,键盘或鼠标的驱动程序将会把输入转换成消息,然后将它们存入系统消息队列中,系统不停地从系统消息队列中取出消息,并根据消息的窗口句柄确定目标窗口是哪个,然后将该消息传送到目标窗口所对应的用户界面线程中去。而每个线程又从线程的消息队列中取出消息,然后告知系统该执行哪个消息处理函数。(1)用队列的发送方式大部分Windows消息都是通过以先进先出
(FIFO)的方式在消息队列中传送,但WM_PAINT消息在消息队列中要被特殊处理。当WM_PAINT消息被发出时,意味着窗口的客户区将要被重画,如果有若干个WM_PAINT消息被连续发出时,消息队列的某一段将全部是WM_PAINT消息,这将导致窗口客户区被毫无意义地重画若干遍,从而降低了程序的运行速度,因此,Windows系统将所有这些WM_PAINT消息合并成一个,使窗口只被重画一次。向应用程序窗口的线程发送消息时,Windows系统将生成一个MSG结构的对象,然后填充该结构的对象,再将该结构对象发送到线程的消息队列中去。其中,MSG结构的定义为:(1)用队列的发送方式
typedef
struct
tagMSG( HWNDhwnd, //目标窗口的句柄
UINTmessage, //消息的标识值
WPARAMwParam, //消息参数
LPARAMlParam, //消息参数
DWORDtime, //消息发送时的时间
POINTpt, //消息发送时的光标位置(屏幕坐标)
)MSG;可见,MSG结构中存储了消息的全部信息。(1)用队列的发送方式而线程可以利用PostMessage()函数或PostThreadMessage()函数向自己的消息队列或其他线程的消息队列发送消息。其中,PostMessage()函数的原型为:BOOLPostMessage(HWNDhWnd, //目标窗口的句柄,当hWnd取为
//HWND_BROADCAST时,消息将发给系统当前
//的所有窗口;当hWnd取为NULL时,消息将
//发送给当前线程自己的消息队列
UINTMsg, //将要发送的消息标识
WPARAMwParam, //第一个消息参数
LPARAMlParam, //第二个消息参数);当消息发送成功时,PostMessage()函数返回非0值。PostThreadMessage()函数原型为:BOOLPostThreadMessage(DWORDidThread, //将要接收消息的线程标识
UINTmes, //消息标识
WPARAMwParam, //第一个消息参数
LPARAMlParam, //第二个消息参数);当消息发送成功时,PostMessage()函数返回非0值。(1)用队列的发送方式如果读者想从消息队列中取出一个消息,可以调用GetMessage()函数;如果仅仅想查看消息队列而不打算从消息队列中将消息取出,可以调用PeekMessage()函数。无论调用上述哪个函数,最终都可以获取一个MSG结构的对象。GetMessage()函数的原型为:BOOLGetMessage(LPMSGlpMsg, //指向MSG结构对象的指针
HWNDhWnd, //窗口句柄,该窗口的消息将被取出
UINTwMsgFilterMin, //定义了消息队列中将要被取走的消息标识
//值的下限
UINTwMsgFilterMax, //定义了消息队列中将要取走的消息标识
//值的上限。
);(1)用队列的发送方式
当GetMessage()函数获取的消息不是WM_QUIT消息时,函数的返回值为非0;如果函数调用过程中出错,则返回-1。GetMessage()函数只能对调用它的线程所对应的消息队列进行操作,对其他线程对应的消息队列无法进行操作。只有wMsgFilterMin和wMsgFilterMax之间的消息才会被取走,如果两者都为0,则不进行消息过滤。此外,当GetMessage()函数获取的消息是WM_QUIT时,WM_QUIT消息不会像其他消息一样被删除,该消息将一直存在于消息队列中,直到WM_QUIT消息的消息处理代码被执行了以后才会被删除。由于GetMessage()函数可能返回正数、0、负数,因此,使用该函数时应该小心,因为有时会产生错误。当应用程序从消息队列中取出消息后,就可以调用DispatchMessage()函数向应用程序发送消息了。DispatchMessage()函数向消息处理函数发送当前消息中的目标窗口句柄、消息标识和两个消息参数,但不发送消息发送时间和鼠标光标位置。用户可以通过GetMessageTime()函数来控制另外一个线程,该函数被调用后,当前线程将被挂起,直到有消息进入线程的消息队列。(2)不使用队列的发送方式采用这种方式发送的消息将直接被传送给消息处理函数,不再进入系统或线程中的消息队列,这类消息被用于通知窗口有对其产生影响的事件发生。例如,当用户激活一个窗口后,系统将发送一系列消息,即WM_ACTIVE、WM_SETFOCUS和WM_SETCURSOR消息,用于通知窗口已经被激活,键盘产生的消息将针对该窗口,并且鼠标在当前窗口中运动。此外,用户调用了某些函数也会使系统产生不通过消息队列的消息,例如,用户调用了SetWindowPos()函数后,系统将直接向消息处理函数发送WM_WINDOWPOSCHANGED消息,用于通知窗口其位置发出了变化。
1.消息的传递不同类型的消息是由操作系统和应用程序的不同部分来处理的,例如在窗口中移动鼠标,窗口会发出一个WM_MOUSEMOVE消息,操作系统接收后,将在一个新的位置重绘一个鼠标。当用户用鼠标左键单击一个按钮时,按钮将发出一个WM_LBUTTONDOWN消息,控件自身对消息进行处理,同时按钮还向包含它的窗口发出被单击的消息,由窗口对象截获后,进行处理。2.消息的循环
Windows程序的核心是消息循环,消息不断地被Windows操作系统截获,然后在应用程序中加以处理。在利用WindowsAPI编写的应用程序中,Windows的消息循环包含在WinMain()函数中,WinMain()在Windows程序中的作用相当于基于DOS或UNIX的C程序里的main()函数,WinMain()是Windows程序的入口。下面一段代码是典型的WindowsAPI应用程序实例:2.消息的循环
//WinMain()函数初始化应用程序,并形成消息循环
intAPIENTRYWinMain(HINSTANCE
hInstance,HINSTANCEhPreInstance,LPSTRlpCmdLine,
Int
nCmdShow){MSGmsg;//初始化应用程序
if(!InitApplication(hInstance)) returnFALSE;
if(!InitInstance(hInstance,nCmdShow))returnFALSE;//形成消息循环
while(GetMessage(&msg,NULL,0,0)) //截获消息,并为
//msg结构赋值
{
TranslateMessage(&msg); //将虚键消息转换成字符
DispatchMessage(&msg); //发送消息;}returnmsg.wParam;}2.消息的循环在上面的代码中,InitApplication()和InitInstance()都是用于初始化应用程序的函数。通常在这两个函数中调用RegisterWindow()来注册应用程序窗口,也就是设定应用程序主窗口的属性,同时确定用于处理Windows消息的函数,最后,还要显示和绘制应用程序主窗口。示例代码中处理Windows消息的函数是WndProc()。首先在InitInstance()中调用CreateWindow()函数创建应用程序主窗口,然后调用GetMessage()函数,形成消息循环。GetMessage()函数只有在收到WM_QUIT消息才返回FALSE,或者说,只有WM_QUIT可以终止应用程序的消息循环。TranslateMessage()是专门用于处理键盘消息的函数,然后调用DispatchMessage()函数,该函数通过调用WndProc()函数来处理消息。如上所述,WndProc()函数是在注册窗口时被确定为消息处理函数的。下面是典型的消息处理函数的实例:2.消息的循环
LONGAPIENTRYWndProc(HWND
hWnd, //接收消息的窗口句柄
UINTmessage, //消息标识
UINTwParam, //第一个消息参数
LONGlParam) //第二个消息参数
{
switch(message){caseWM_MOUSEMOVE://处理鼠标移动的消息
break;caseWM_LBUTTONDOWN://处理点击鼠标左键的消息
break;caseWM_RBUTTONDOWN:
//处理点击鼠标右键的消息
break;caseWM_PAINT://重画窗口
break;caseWM_COMMAND:
//处理命令消息
switch(LWORD(wParam))
2.消息的循环
{caseBN_CLICKED:……//处理单击按钮消息
break;caseBN_DOUBLECLICKED:……//处理双击按钮消息
break;}break;default:break;}break;caseWM_DESTROY:
//在窗口被关闭前进行处理
PostQuitMessage(0); //终止消息循环,退出应用程序;
break;default:returnDefWindowProc(hWnd,message,wParam,lParam);}return0;}2.消息的循环由以上代码可以看出,WndProc()函数的作用主要是进行消息处理,并且消息处理是通过许多繁杂的Switch-Case判断语句来实现的,所以基于WindowsAPI的应用程序开发是一件十分繁琐的事情,因为所有事情都必须亲自完成,而其中的许多事情又是每一个应用程序必须做的,因此包含大量重复工作。为了简化Windows应用程序的开发,VC++将WindowsAPI函数进行了封装,产生了MFC类库。为了进一步减少用户在处理Windows程序时所花费的工作,VC++集成环境又提供了“类向导”工具,使用类向导可以方便地进行消息映射等工作,将过去复杂的编码工作变得非常简单了。4.1.3消息映射机制消息映射是MFC为Windows应用程序的消息处理提供的一种框架,它比在传统的Windows应用程序下控制消息处理的switch-case语句和if-else语句更为简洁。在MFC中,大部分类都是由CCmdTarget类派生出来的,在CCmdTarget类中实现了消息映射,因此都可以拥有自己的消息映射,消息映射体现了C++语言中类的封装性。1.消息映射的组成
消息映射由两部分组成:一个是在类的头文件(.h文件)中,一个是在类的.cpp文件中。它们的框架通常由“应用程序向导”产生,用户只需在消息响应函数中添加消息处理代码。第2章S2_2应用程序在S2_2.h文件中定义的消息映射,是用于处理帮助菜单下的“关于”菜单项的消息映射,程序代码如下:1.消息映射的组成
virtualvoidPreLoadState();virtualvoidLoadCustomState();virtualvoidSaveCustomState();
afx_msgvoidOnAppAbout();DECLARE_MESSAGE_MAP()
在以前的VC++版本中这段代码有特定格式,“类向导”根据这些特定格式跟踪类中定义的所有消息,在这段代码中声明了消息处理函数OnAppAbout(),类型为afx_msg。DECLARE_MESSAGE_MAP()是一个由C++编译器预处理扩展的消息映射宏,它声明了一个队列,此队列会装入消息映射入口和一些用于找到基类的消息映射的指针。1.消息映射的组成
在单文档应用程序的应用程序类(如S2_2.cpp)的实现中,定义了消息映射中消息映射函数与控件ID的关联,并给出了具体的消息映射函数的处理代码,消息映射中的消息映射函数与控件ID关联如下所示:1.消息映射的组成BEGIN_MESSAGE_MAP(CS2_2App,CWinAppEx) ON_COMMAND(ID_APP_ABOUT,&CS2_2App::OnAppAbout) //基于文件的标准文档命令
ON_COMMAND(ID_FILE_NEW,&CWinAppEx::OnFileNew) ON_COMMAND(ID_FILE_OPEN,&CWinAppEx::OnFileOpen) //标准打印设置命令
ON_COMMAND(ID_FILE_PRINT_SETUP,&CWinAppEx::OnFilePrintSetup)END_MESSAGE_MAP()//……//用于运行对话框的应用程序命令voidCS2_2App::OnAppAbout(){
CAboutDlg
aboutDlg;
aboutDlg.DoModal();}1.消息映射的组成
ID_APP_ABOUT是“关于”菜单项的资源ID,根据ON_COMMAND(ID_APP_ABOUT,&CS2_2App::OnAppAbout)消息映射语句,应用程序知道用户单击“关于”菜单项时,就可以调用OnAppAbout()函数,也就是显示一个关于应用程序信息的对话框。在这段代码中,读者看到了许多类似BEGIN_MESSAGE_MAP、ON_COMMAND、END_MESSAGE_MAP代码,这些就是我们要介绍的消息映射宏。2.消息映射宏
消息映射宏声明了一组程序框架,以便在应用程序中查找所有消息映射的成员变量和成员函数。如上面程序片段中提到的DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、ON_COMMAND、END_MESSAGE都属于消息映射宏。VC++还提供了许多用于消息映射的宏,下面介绍常用的消息映射宏和作用。(1)DECLARE_MESSAGE_MAP:用于在头文件中声明类中含有消息映射函数。(2)BEGIN_MESSAGE_MAP:在cpp文件中标记消息映射的开始。(3)END_MESSAGE_MAP:在cpp文件中标记消息映射的结束。(4)ON_COMMAND:将一个菜单项或工具栏按钮等发出的命令消息传给一个特定的消息处理函数。(5)ON_COMMAND_RANGE:将多个命令ID映射到一个消息处理函数,即该消息映射函数处理所有这些命令消息。(6)ON_CONTROL:当一个定制控件向它的父窗口发送通知消息时,指定哪一个成员函数来处理该通知消息。(7)ON_CONTROL_RANGE:将一组控件发送给父窗口的通知消息,分配给一个特定的消息处理函数来处理。(8)ON_MESSAGE:指定处理用户自定义消息的成员函数。2.消息映射宏(9)ON_REGISTERED_MESSAGE:指定处理在系统中注册消息的成员函数。(10)ON_UPDATE_COMMAND_UI:指定处理命令更新消息的成员函数。(11)ON_COMMAND_UPDATE_UI:用于将一组命令更新消息映射到一个成员函数,由这个成员函数处理所有的更新消息。(12)ON_NOTIFY:指定处理控件或子窗口向父窗口发送的通知消息的成员函数。(13)ON_NOTIFY_RANGE:将一组控件或子窗口向父窗口发送的通知消息映射到一个成员函数,由这个成员函数处理所有的通知消息。(14)ON_NOTIFY_EX:指定处理控件或子窗口向父窗口发送的通知消息的成员函数,与ON_NOTIFY不同的是,该成员函数会返回一个BOOL变量,返回FALSE表示该函数可以继续处理其他控件发出的同样的通知消息。(15)ON_NOTIFY_RANGE_EX:将一组控件或子窗口向父窗口发送的通知消息映射到一个成员函数,同样该函数也会返回一个BOOL变量,以确定消息处理过程是否要继续。
VC++中有100多个消息映射宏,每一个都对应着常用的消息,并给消息指定消息处理函数,通常这些宏都是由“类向导”自动添加到用户的消息映射中的。3.消息映射的处理过程通过理解Windows是如何处理消息映射,可以帮助读者理解消息映射的含义。在消息循环一节中,我们提到,应用程序通过调用Run()函数来等待消息,用户操作发出特定的消息会被GetMessage()函数得到,然后调用函数PreTransalteMessage()、TranslateMessage()和DispatchMessage(),最终消息将被发送给函数WndProc()。在WndProc()函数中,它调用了函数OnWndMsg(),这是一个C++中真正处理消息的函数。首先函数会检查参数是否代表一个消息、一个命令或者是一个通知消息,并确定消息要发送到的窗口,然后函数会自动在窗口对应的类中查看由消息映射宏DECLARRE_MESSAGE_MAP、BEGIN_MESSAGE_MAP和END_MESSAGE_MAP等定义的消息映射,这些消息映射提供了处理消息的入口,如果OnWndMsg()函数在这些定义的消息映射中找到了入口,消息就会被送往消息映射定义的消息处理函数,从而结束消息处理过程。如果消息在该类中没有入口,WndProc()会在该类基类中定义的消息映射中进行查找,并沿这个层次往上移,直到找到入口为止。这充分利用了C++中的继承特性。3.消息映射的处理过程
MFC为很多消息的捕获和处理提供了代码,用户一般不需要自己编程。用户所要做的工作仅仅在于添加对特定消息的处理代码。下面以单文档程序为例来说明消息映射的处理过程。在单文档应用程序的“帮助”菜单中有一个“关于”菜单项,选择它会发出一个ID_APP_ABOUT命令消息,并传送给应用程序的SDI主框架。随后经历了以下过程:(1)对于单文档应用程序,由主框架接收消息。在多文档应用程序中,主框架会首先检测活动的子框架。(2)主框架首先检测活动视图,由视图检测自己是否有该命令消息的消息映射。(3)然后检测文档,文档会检测自己是否有该命令消息的消息映射。(4)回到主框架(对多文档应用程序,首先会到子框架),主框架将检测自己是否有该命令消息的消息映射。(5)最后,检测应用程序的消息映射,检测是否有该命令消息的消息映射。本例中ID_APP_ABOUT命令消息将在应用程序的消息映射中找到消息入口,并执行宏ON_COMMAND指定的消息处理函数OnAppAbout()。主要内容4.2文档与视图结构4.3文档与视图类4.4特殊视图类4.5文档与视图应用4.1消息映射机制4.2文档与视图结构4.1消息映射机制4.2.1概述
VC++有两种类型的应用程序与文档视图有关,一种是单文档应用程序,另一种是多文档应用程序。无论哪一种,都可以使用文档存储应用程序数据,用视图显示和更改数据。在基于文档视图结构的应用程序中,CDocument类的派生类充当文档角色,文档类不仅可以存储数据,而且可以对数据进行串行化操作,即对数据进行输入输出操作。而视图则由CView类的派生类承担,视图类有多个类型,最基本的是CView类,其次还有CScrollView类,这种类型的视图允许滚动视图,使显示比当前视图窗口还大的图形成为可能。CFormView是一个和对话框资源相关联的类,在CFormView的视图窗口上,可以像使用对话框一样添加控件,实现数据输入和对数据的变更。CEditView允许使用者不编一句代码就可以实现一个类似于Windows记事本的应用程序。CTreeView则显示一个树状控件,用户可以像树状控件一样使用视图。CHtmlView显示浏览器窗口,CListView显示一个列表控件,用户可以像列表控件一样使用视图。CRichEditView显示一个RichEdit控件,用户可以像RichEdit控件一样使用视图,不编写一句代码就可以实现一个类似于写字板的应用程序。一个文档可以有多个视图与之对应,这就是所谓的“单文档多视图”,单文档多视图可以使应用程序用一个视图进行数据输入,另外一个视图用于数据显示。应用程序创建时,首先,创建一个文档模板,文档模板决定了文档的格式,一个应用程序可以有多个文档模板,每个文档模板对应一个文档;其次,在文档模板中创建一个框架窗口;最后,在框架窗口中创建视图。可见,视图实际上是“贴在”框架窗口上的窗口。4.2.2Document/View结构
Document/View结构的基本出发点是把数据处理从用户界面中分离出来,使得每一部分都能集中执行一项任务。视图负责提供一个屏幕窗口,用户通过它可以看见并管理文档,文档负责控制和存储所有数据,下面以单文档应用程序为例介绍文档/视图结构。“应用程序向导”生成文档/视图结构的应用程序时,把程序分成4个主要类:(1)从CDocument派生的文档类:负责程序中的数据。(2)从CView派生的视图类:处理文档和用户之间的交互。(3)从CFrameWnd派生的框架窗口类:包含视图和其他用户界面控件,如菜单、工具栏、状态栏等。(4)从CWinApp派生的应用程序类:负责启动关闭程序,处理应用程序启动时的初始化工作和结束时的清理工作。4.2.2Document/View结构
Document/View结构适用于大多数的Windows程序,其最大的优点是把Windows程序通常要做的工作分配到若干个预先定义好的类中,这样有助于应用程序的模块化,提高了用户对程序设计和管理的效率。此外使用Document/View结构的应用程序也易于扩展,只需要修改所涉及的类即可,减少了维护程序的工作量。4.2.2Document/View结构创建基于文档视图结构的应用程序时,首先生成文档模板对象,然后调用CWinApp::AddDocTemplate()函数创建一个文档模板,示例代码如下:BOOLCMyApp::InitInstance(){……CSingleDocTemplate*pDocTemplate;pDocTemplate=newCSingleDocTemplate(IDR_MAINFRAME, //文档模板的资源ID
RUNTIME_CLASS(CMyDoc), //文档类
RUNTIME_CLASS(CMainFrame), //框架窗口类
RUNTIME_CLASS(CmyView) //视图类
); //生成文档模板对象;AddDocTemplate(pDocTemplate); //创建文档模板;……}4.2.2Document/View结构从上面的代码可以看出,每个文档模板由对应的资源(字符串资源、菜单资源、图标等)、文档、视图和框架窗口4个要素组成。但是,并没有通过指针或对象将文档、框架窗口和视图传递给应用程序类,而是通过RUNTIME_CLASS()宏将类名传递给应用程序。RUNTIME_CLASS()宏使应用程序在运行时动态生成上述类的对象,并使上述类的对象支持文档/视图结构。但是,要使RUNTIME_CLASS()宏起作用,还必须在类的头文件中加入DECLARE_DYNCREATE()宏,在类的实现文件中添加IMPLEMENT_DYNCREATE()宏。例如,在SDI应用程序主窗口CMainFrame类的头文件中可以看到如下代码:4.2.2Document/View结构……classCMainFrame::public
CFrameWnd{protected://createfromserializationonly
CMainFrame();
DECLARE_CYNCREATE(CMainFrame)……};……在SDI应用程序CMainFrame
类的实现文件中可以看到如下代码:……//////////////////////////////////////////////////////////////////CMainFrame
IMPLEMENT_DYNCREATE(CMainFrame,CFrameWnd)……同样,在文档和视图类中还可以看到同样的代码。4.2.2Document/View结构另一方面,一个应用程序可以有多个文档模板,文档模板的作用是自动处理文档、视图及框架窗口的关系。对基于SDI的应用程序,一次只能打开一个文档,而对MDI的应用程序,一次可以打开多个文档。但是,无论哪种应用程序,一个文档模板一次只能有一种文档,而每个文档又可以有多个视图,这些对象之间的关系如图4-1所示。4.2.2Document/View结构主要内容4.2文档与视图结构4.3文档与视图类4.4特殊视图类4.5文档与视图应用4.1消息映射机制4.5文档与视图应用4.2文档与视图结构4.1消息映射机制4.3文档与视图类
文档类在基于文档/视图结构的应用程序中主要用来操作数据,它可以完成大部分数据管理功能。当用户单击“文件|新建”菜单项或直接单击工具栏上的“新建”时,MFC都会自动调用OnNewDocument()函数对新建文档进行初始化。当打开一个旧文档或者保存一个修改的文档时,都会调用Serialize()函数。此外还有两个用于调试的虚函数AssertValid()和Dump(),在调试应用程序时,这两个函数用来输出错误诊断信息。这两个函数只在应用程序的Debug版本中执行,在Release版本中不会执行。4.3文档与视图类
文档类可以保存应用程序中使用的数据,具体来说,它可以实现如下功能:(1)为视图提供数据。(2)为视图提供数据改变信息。(3)保存文档中的数据。(4)为打印提供文档数据。一般来说,文档数据包含在文档类的数据成员中,这时可以充分利用MFC提供的序列化功能以及基类CDocument
提供的打开文件、保存文件功能。用户通过对下列函数的重写,就可以按自己的要求处理数据了。
Document/View结构使用序列化来保存和打开文档。当一个文档被保存或装载时,MFC创建一个CArchive对象,通过它序列化该文档。当文档被打开时,系统创建一个CArchive对象以进行读操作,对应的文档文件被读入内存;当文档被保存时,系统创建一个CArchive对象以进行写操作,并且将文档写入外存文件中;其他时刻,CDocument类跟踪文档数据的当前状态,如果文档被修改,系统将在关闭文件时提示用户是否保存。1.保存文档当用户选择“保存文档”或者“文档另存为”时,MFC调用CDocument::OnSaveDocument函数(选择“文档另存为”时,先打开文档对话框获得文档名,再调用CDocument::OnSaveDocument函数)来序列化文档数据。通常情况下,OnSaveDocument函数会创建一个CArchive对象,并把它传递给文档的Serialize成员函数,在Serialize函数中序列化文档数据。序列化文档数据后,设置文件修改标志为假。具体的函数调用关系如下:CWinApp:OnFileSave{……pDoc->OnSaveDocument();……}1.保存文档CDocument::OnSaveDocument(){CArchive
ar;……Serilaize(ar);SetModifiedFlag(FALSE);}CDocument::Serialize(CArchive&ar){if(ar.IsStoring())
//保存文件
ar<<m_Variable; //将文档数据保存到文件中else //打开文件
ar>>m_Variable
//从文档中读取数据到内存变量}Serialize()函数主要用来序列化需要打开和保存的数据。函数的参数ar是一个CArchive类的对象,它可以将对象用二进制形式保存(通常在磁盘上),还可以从存储设备中装载这些对象,在内存中重建它们,这种使数据保存和装载的过程称为“串行化”或“序列化”。对象ar的一个成员函数IsStoring()返回一个BOOL类型值,用来判断当前的序列化是在保存数据还是在读取数据,根据这个判断,通过ar的操作符“<<”来将数据保存,通过操作符“>>”来装载数据。2.关闭文档
关闭文档时,MFCDocument/View框架调用文档类的OnCloseDocument成员函数,该函数缺省调用IsModified函数来检测文档是否做过修改,如果没有任何修改,就调用DeleteContents释放文档存储的数据,关闭与文档有关的所有视图;如果对文档做了编辑,则程序显示一个提示框,询问是否保存所做的编辑,此时,如果选择保存文档,则Serialize函数被调用,然后使用DeleteContents函数释放文档存储的数据,最后关闭与文档有关的所有视图。3.打开文档
打开或装载文档时,MFCDocument/View框架调用文档类的OnOpenDocument成员函数。该函数先调用DeleteContens函数,然后调用Serialize函数装载文档数据,调用过程如下:CWinApp::OnFileOpen(){……pDoc->OnOpenDocument();……}CDocument::OnOpenDocument{……DeleteContents();Serialize();……}CDocument::Serialize(CArchive&ar){if(ar.IsStoring())
//保存文件
ar<<m_Variable; //将文档数据保存到文件中else //打开文件
ar>>m_Variable
//从文档中读取数据到内存变量}3.打开文档
DeleteContents()函数用于删除文档数据但并不销毁文档对象本身,它由框架调用,该函数在删除文档之前调用,也可在复制文档对象前调用以确保文档为空,这对SDI应用程序尤其重要,因为SDI应用程序只有一个文档对象,无论何时用户创建或打开另一个文档,都需要复制当前的文档对象。
ReportSaveLoadException()函数的默认实现是检查对象异常,并查找描述此异常原因的错误消息。如果找到特定消息,则使用指定的错误消息,接着函数弹出包含此错误消息的消息框。如果应用程序想提供定制的错误消息,可在派生类中重写此函数。
SaveModified()函数的默认实现是显示一个消息框,询问用户是否保存对文档的修改(假设对文档进行了修改)。如果应用程序需要不同的提示,则可在派生类中重写此函数。3.打开文档
SetModifiedFlag()函数,当文档数据被改变时,调用该函数可以让文档“记住”文档已更改,尚未存盘。该函数原型为:
voidSetModifiedFlag(BOOL
bModified=TRUE);
当该函数的形参bModified为TRUE时,标记文档处于已经修改尚未存盘的状态;当该函数的形参bModified为FALSE时,标记文档处于未修改状态。
GetFirstViewPosition()函数:该函数用于获取对应于该文档的第一个视图的位置,它返回一个POSITION结构的对象,用于记录第一个视图的位置。
GetNextView()函数,该函数原型为:
CView*GetNextView(POSITION&rPosition);3.打开文档
该函数的形参是POSITION结构对象的引用,该函数可根据当前视图的位置获取下一个视图的位置,并且是通过引用类型的形参返回,该函数的返回值是当前要查找视图的指针。该函数和GetFirstViewPosition()函数一起使用,可以获取所有与文档模板相关联的视图,从而实现针对各个视图的操作,如下所示:POSITIONpos=GetFirstViewPosition();while(pos!=NULL){
CView*pView=GetNextView(pos);
pView->UpdateWindows();}UpdateAllViews()函数,该函数用于文档数据改变之后,更新所有视图,该函数原型为:
voidUpdateAllViews(
CView*pSender,LPARAMlHint=0L,
CObject*pHint=NULL);3.打开文档该函数形参含义如下:
pSender:指向发生变化的文档视图指针,除了该指针所指的视图之外的其他视图均会被更新,如果该形参为NULL,则所有视图被更新。
lHint:存储关于更新视图的附加信息,默认值为NULL。
pHint:存储关于更新视图的附加信息,但该信息存储在CObject类对象中,默认值为NULL。该函数将调用除了第一个形参所指定的视图之外的所有视图的OnUpdata()函数,同时将与更新有关的参数传递给各个需要更新视图的OnUpdate()函数,并且可以在每一个视图中重写OnUpdate()函数,然后根据UpdateAllView()函数传来的参数进行特殊的更新处理。4.3.2视图类
视图类在基于文档/视图的应用程序中用于显示存储在文档对象中的数据,并允许用户修改这些数据。视图类被连接到一个文档,其作用是作为文档和用户的中介。一个视图只能与一个文档相连接,负责多种不同类型的输入。视图类必须重写CView类的OnDraw()函数以完成文档在屏幕上的显示。当一个文档的数据发生变化时,与文档相连的视图类通常需要调用CDocument::UpdateAllViews函数,该函数通过调用其他每个视图的OnUpdate()成员函数来通知其他所有视图数据的变化。4.3.2视图类视图获取文档指针的方法是引用CView
类中的m_pDocument数据成员,m_pDocument是定义在CView类中的CDocument类对象。在视图类中获取文档指针的函数GetDocument()的实现代码如下所示,该函数通常是由“应用程序向导”自动产生。CMyDoc*CMyView::GetDocument(){
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyDoc)));Return(CMyDoc*)m_pDocument;}获取文档指针后,就可以使用图、列表等形式显示文档数据,也可以通过视图上的控件对文档数据进行输入和修改,下面介绍视图类的显示和更新过程:
CView类是最通用的视图类,其派生类窗口主要用于绘图操作,该类最常用的几个函数介绍如下:(1)OnInitialUpdate()函数:该函数在视图与文档关联之后,视图显示之前,由框架窗口调用,在CView类派生类中重写该函数可以实现一些视图的初始化工作。在CView类中,该函数将调用OnUpdate()函数,而OnUpdate()函数将发出WM_PAINT消息,使整个视图绘制一遍。4.3.2视图类(2)OnDraw()函数:在CView类的派生类中,重写该函数可实现对文档数据显示,该函数用于实现屏幕显示,打印和打印预览,在不同情况下,使用的设备描述表也不一样,用户可以使用pDC参数所指向的CDC对象进行不同图形设备上的数据显示,程序清单如下所示:voidCMyMDIView::OnDraw(CDC*pDC){CMyMDIDoc*pDoc=GetDocument();
ASSERT_VALID(pDoc);//TODO:adddrawcodefornativedatahere}
用户在绘制之前,可将GDI资源(例如画笔和字体)选入设备描述表,在绘制工作完成之后,再从设备描述表中将其移出以供其他程序使用,用户必须重写这个函数显示文档数据。4.3.2视图类(3)OnPrepareDC():在调用OnDraw()函数之前,或者在打印或打印预览期间为每一页调用OnPrint()函数之前,框架均需调用此函数。该函数在默认情况下,对屏幕显示没有任何动作。若为打印调用此函数,它将检查存储在参数中的页信息,默认情况下为一页。(4)OnUpdate():当视图的文档被修改后,框架将调用此函数。
CDocument::UpdateAllViews();
调用此函数允许视图更新其显示来反映修改的结果,此外OnInitialUpdate()函数的默认实现也调用此函数。该函数默认实现是在收到下一条WM_PAINT消息时使整个客户区无效,并将客户区作上绘制标记,当用户希望只修改文档已修改部分对应的区域时,可重写此函数。4.3.2视图类主要内容4.2文档与视图结构4.3文档与视图类4.4特殊视图类4.5文档与视图应用4.1消息映射机制4.5文档与视图应用4.2文档与视图结构4.1消息映射机制在MiniDraw应用程序中用到的视图类是由CView类直接派生的,除了CView类外,MFC还提供了一些不同的视图类,这些类都是从CView类派生的,这些类为视图和相关文档提供了很多特殊的功能,用户可以直接从这些视图类派生自己的视图类,从而拥有这些功能。下面分别介绍这些特殊的视图类。4.4特殊视图类
CScrollView类是一个具有滚动功能的视图类,它具有以下功能:(1)管理窗口和视口的大小以及映射方式。(2)响应滚动条消息而自动滚动。调用该类的成员函数SetScrollSizes()可设置视图对象的大小,包括可滚动视图的尺寸以及在水平或垂直方向上滚动的尺寸,所有尺寸都以逻辑单位给出。除了滚动之外,滚动视类还可以将视图的大小比例变换成当前窗口的大小,在这种方下,该视图没有水平滚动条且该逻辑视图被扩大或缩小以精确适合窗口的客户区,可以调用成员函数SetScaleToFitSize()来完成这个功能。4.4.1滚动视类
CCtrlView类是一个控件视图类,一些基于Windows控件的视类都是从它派生出来的,例如CEditView、CRichEditView、CListView和CTreeView都是由该类派生,一般用户不必使用CCtrlView类来派生自己的视图类。4.4.2控件视类4.4.3编辑视类
CEditView类是由CView派生的,因此该类的对象可以用于文档和文档模板。每个CEditView控制的文本保存在自己的全局对象中,应用程序可以拥有任意数量的CEditView对象。
CRichEditView
类又是从CEditView派生,同时添加了许多特性,包括段落格式(例如居中、左右对齐等)、字符属性(下划线、黑体、斜体等),以及设置边界、字体和页面尺寸等特性。主要内容4.2文档与视图结构4.3文档与视图类4.4特殊视图类4.5文档与视图应用4.1消息映射机制4.5文档与视图应用4.2文档与视图结构4.1消息映射机制本节通过一个具体实例,向读者演示文档视图应用程序开发的详细步骤,通过实际练习,可进一
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2027年备考会计基础 【零基础学员必练】详细试题及答案一
- 2026年湖南铁路科技职业技术学院高职单招笔试数学历年参考题库含答案解析
- 2026年淮安信息职业技术学院高职单招笔试数学历年参考题库含答案解析
- 2026年浙江安防职业技术学院高职单招笔试语文历年参考题库含答案解析
- 2026年泸州职业技术学院高职单招笔试化学历年参考题库含答案解析
- 2026年河南轻工职业学院高职单招笔试物理历年参考题库含答案解析
- 2026年河南工业和信息化职业学院高职单招笔试英语历年参考题库含答案解析
- 2026年河北旅游职业学院高职单招笔试物理历年参考题库含答案解析
- 2026年江西科技学院高职单招笔试数学历年参考题库含答案解析
- 2026年江西交通职业技术学院高职单招笔试职业技能测验历年参考题库含答案解析
- 2026国家电网公司北京电力医院招聘考试模拟试题及答案解析
- 九师联盟2027届高三11月联考历史+答案
- 体外诊断试剂药品培训试题及答案(新版)
- 湖南雅礼中学2027届高三月考试卷三语文试卷
- 2026-2027学年科粤版九年级上册化学期中测试卷解析版
- 《95128出租汽车电召平台服务规范(征求意见稿)》编制说明
- 2026年选矿工程师试题及答案
- 中国区域国别学全套教学课件
- 超声造影剂安全使用专家共识(2026版)
- JJG 1189.1-2026 测量用互感器检定规程 第1部分:标准电流互感器
- 移动式操作平台(盘扣式)专项施工方案(品茗验算通过可套用)
评论
0/150
提交评论