VCMFC编程实例03.docx
《VCMFC编程实例03.docx》由会员分享,可在线阅读,更多相关《VCMFC编程实例03.docx(19页珍藏版)》请在冰豆网上搜索。
![VCMFC编程实例03.docx](https://file1.bdocx.com/fileroot1/2023-1/5/fd813f84-7142-403a-a1f6-b8adbfb92043/fd813f84-7142-403a-a1f6-b8adbfb920431.gif)
VCMFC编程实例03
本文由sha_shoushou贡献
pdf文档可能在WAP端浏览体验不佳。
建议您优先选择TXT,或下载源文件到本机查看。
下载
下载
第3章消息处理
第1章讨论了MFC用户界面的基本要素:
窗口,窗口类和CWnd;第2章讨论了构成MFC库的其他类,尤其是那些构成MFC应用程序内核的类.在本章中,我们将讨论MFC类和它们的窗口怎样进行互相通信的.我们发现有三种类型的消息:
窗口,命令(Command)和控件通知(ControlNotification),并且这些消息既可以发送(sent),也可以寄送(post);接着,将跟踪一个被MFC窗口进程处理的消息;最后,将讨论重定向消息的方法.
3.1发送或寄送一个消息
第1章已提及,每个窗口使用窗口进程处理发送给它的消息.消息可以来自系统,你的应用程序或别的应用程序.消息告诉窗口进程执行某个任务(如初始化自己,绘制或销毁一个窗口等),或者通知它发生某个事件(如鼠标正单击窗口).系统或应用程序有两种传输消息的方法:
发送消息或寄送消息.3.1.1发送一个消息发送一个消息时,直接调用窗口的窗口进程.通信是即时的,直到窗口进程为调用函数返回一个结果后,应用程序才能继续.3.1.2寄送一个消息寄送一个消息时,把消息发送到拥有那个窗口的应用程序消息队列中.一有空闲,应用程序就搜索消息队列,并在消息队列中处理消息,即从队列中删除它们,并将它们发送到即定窗口.通信将可能延迟,直到目标应用程序获得处理消息的时间.调用函数发送消息后即返回,但结果只是表示消息寄送成功与否,而不是被调用窗口进程的结果(见图3-1).
窗口对象WndProc地址①被发送的消息直接调用该窗口的窗口进程消息泵消息n+1消息n+2消息n+3消息n+4消息队列③当应用程序空闲时,抽出寄送到队列中的消息并调用该窗口的窗口进程
②被寄送的消息延迟在应用程序消息队列中
图3-1发送消息时通信是即时的,而寄送消息时通信可能延迟
3.1.3发送一个消息与寄送一个消息的比较鼠标和键盘消息通常是寄送的,而所有其他消息通常都是发送的.在消息队列中,寄送
下载
有鼠标和键盘消息被处理之后.
第3章消息处理
33
的消息接受特殊的鼠标和键盘处理.通常,应该尽量发送一个消息,除非想把动作延迟到所
3.2怎样使用MFC发送一个消息
用MFC发送一个消息的方法是,首先,应获取接收消息的CWnd类对象的指针;然后,调用CWnd的成员函数SendMessage().
LRESULTRes=pWnd->SendMessage(UINTMsg,WPARAMwParam,LPARAMlParam);
pWnd指针指向目标CWnd类对象.变量Msg是消息,wParam和lParam变量包含消息的参数,如鼠标单击哪里或选择了什么菜单项.目标窗口返回的消息结果放在变量Res中.发送消息到一个没有CWnd类对象的窗口,可以用下列目标窗口的句柄直接调用WindowsAPI:
LRESULTRes=:
:
SendMessage(HWNDhWnd,UINTMsg,WPARAMwParam,LPARAMlParam);
这里的hWnd是目标窗口的句柄.
3.3怎样用MFC寄送一个消息
用MFC寄送一个消息与发送一个消息几乎相同,但寄送时用PostMessage(),而不是用SendMessage();返回值Res也不一样,Res不是一个由目标窗口返回的值,而是一个布尔值,用来表示消息是否成功地放到消息队列中.检索一个寄送消息正常情况下,一旦消息被寄送后,应用程序在后台发送它.但是在特殊情况下,需要你自己去删除一个消息,例如想在应用程序接收到某种消息之前停止应用程序.有两种方法可以从应用程序消息队列中删除一个消息,但这两种方法都没有涉及MFC.
■
第一种方法:
在不干扰任何事情之下窥视消息队列,看看一个消息是否在那里.
BOOLres=:
:
PeekMessage(LPMSGlpMsg,HWNDhWnd,UINTwMsFilterMin,UINTwMsgFilterMax,UINTwRemoveMsg);■
第二种方法:
实际上是等待,一直等到一个新的消息到达队列为止,然后删除并返回该消息.
BOOLres=:
:
GetMessage(LPMSGlpMsg,HWNDhWnd,UINTwMsgFilterMin,UINTwMsgFilterMax);
在这两种方法中,变量hWnd指定要截获消息的窗口,如果该变量设为NULL,所有窗口消息将被截获.wMsgFilterMin和wMsgFilterMax变量与SendMessage()中的变量Msg相对应,指定查看消息的范围.如果用"0,0",则所有的消息都将被截获.如果用WM_KEYFIRST,WM_KEYLAST或WM_MOUSEFIRST,WM_MOUSELAST,则所有键盘或鼠标的消息将被截获.wRemoveMsg变量指定PeekMessage()是否应该真正地从队列中删除该消息.(GetMessage()总是删除消息).该变量可以取两个值:
■■
PM_REMOVE,PeekMessage()将删除消息.PM_NOREMOVE,PeekMessage()将把消息留在队列里,并返回它的一个拷贝.
当然,如果把消息留在消息队列中,然后再次调用PeekMessage()查看相同类型的消息,则将返回完全相同的消息.
34
第一部分基础知识
下载
lpMsg变量是一个指向MSG结构的指针,MSG包含检索到的消息.
typedefstructtagMSG{HWNDhwnd;//windowhandlemessageisintendedforUINTmessage;WPARAMwParam;LPARAMlParam;DWORDtime;//thetimethemessagewasputinthequeuePOINTpt;//thelocationofthemousecursorwhenthe//messagewasputinthequeue}MSG;
3.4三种类型的消息
在MFC应用程序中传输的消息有三种类型:
窗口消息,命令消息和控件通知.3.4.1窗口消息窗口消息(WindowMessage)一般与窗口的内部运作有关,如创建窗口,绘制窗口和销毁窗口等.通常,消息是从系统发送到窗口,或从窗口发送到窗口.当用SendMessage()或PostMessage()发送一个窗口消息时,变量Message,wParam和lParam的格式如下:
MessageWM_XXXwParam定义的命令lParam定义的命令
WM_XXX可以是许多窗口消息之一,如下列窗口:
■WM_CREATE,告诉窗口初始化自己.■WM_PAINT,告诉窗口绘制自己.■WM_MOUSEMOVE,告诉窗口鼠标移经它.
有关某些公共窗口消息,参见附录B.若需要窗口消息的完全的列表,请参考MFC文档.3.4.2命令消息命令消息一般与处理用户请求相关,当用户单击一个菜单项或工具栏时,命令消息产生,并被发送到能处理该请求的类对象(如,装载文件,编辑文本和保存选项等).当用SendMessage()或PostMessage()发送窗口消息时,变量Message,wParam和lParam的格式如下:
MessageWM_COMMAND0wParamCommandIDlParam0
CommandID要么是选中菜单项的ID,要么是被单击的工具栏按钮.注意CommandID不能大于一个字长,如果使它大于一个字长,系统就只用0来填充高位字.某些控件通知也用WM_COMMAND消息,区别两种消息的唯一方法是lParam是否为NULL.3.4.3控件通知通常,控件通知在某些重要事件发生时,由控件窗口发送到父窗口,如打开一个组合框.
下载
组合框初建时得不到的消息填充它.
第3章消息处理
35
控件通知为父窗口进一步控制子窗口提供了机会.例如,打开一个组合框时,父窗口可以用控件通知经历了一个演变过程,因而SendMessage()的变量Message,wParam和lParam有三种格式.1.第一控件通知格式第一控件通知格式只是窗口消息的子集.
MessageWM_XXXwParam定义的命令lParam定义的命令
WM_XXX可以是下面消息中的任何一种:
■
WM_PARENTNOTIFY表示一个控件窗口要么已被建立或销毁,要么鼠标已单击了该
窗口.
■WM_CTLCOLOR,WM_DRAWITEM,WM_MEASUREITEM,WM_DELETEITEM,
WM_CHARTOITEM,WM_VKEYTOITEM或WM_COMPAREITEM都是送往父窗口的消息,用来绘制自身的控件窗口.
■WM_HSCROLL或WM_VSCROLL由滚动条控件发送,通知父窗口滚动窗口.
2.第二控件通知格式第二控件通知格式使用WM_COMMAND消息,与命令消息共享.
MessageWM_COMMANDwParamXN_XXX控件IDlParam窗口句柄
lParam变量用来区分是命令消息还是控件通知.控件通知在lParam中有一个有定义的句柄,用来标识发出通知的控件;而命令消息中lParam为NULL.XN_XXX值因发出通知的控件不同而不同,例如,XN_XXX值为EN_CHANGE,告诉父窗口显示在编辑框控件中的文本已发生变化.还有其他一些例子列在附录B中.3.第三控件通知格式第三控件通知格式也是最灵活的通知格式,它用WM_NOTIFY消息.
MessageWM_NOTIFYwParam控件IDlParam指向NMHDR的指针
lParam值指向一个结构,该结构包括有关制作该通知的控件的任何内容,而不受空间和类型的限制,该结构叫做NMHDR.
typedefstructtagNMHDR{HWNDhwndFrom;//WindowhandleofControlWindow//makingthenotification.UINTidFrom;//ControlIDofControlWindow//makingthenotification.UINTcode;//notificationcodeex:
theuser//hasclickedtheControlWindow}NMHDR;
NMHDR代表通知消息头(NotificationMessageHeader).为什么要这个头?
因为某些控件用NMHDR作为头发送一个更大结构的消息,即使那些不知道更大结构内容的函数还是能处理
36
通知头.
第一部分基础知识
下载
3.5MFC怎样接收一个寄送的消息
MFC处理一个寄送和发送消息的唯一明显不同是寄送的消息要在应用程序的消息队列中花费一些时间.在消息泵(messagepump)弹出它之前,它要一直在队列中.消息泵MFC应用程序中的消息泵在CWinApp的成员函数Run()中.应用程序开始运行时,Run()就被调用,Run()把时间分割成两部分.一部分用来执行后台处理,如取消临时CWnd对象;另一部分用来检查消息队列.当一个新的消息进来时,Run()抽取它—即用GetMessage()从队列中取出该消息,运行两个消息翻译函数,然后用DispatchMessage()函数调用该消息预期的目标窗口进程(见图3-2).
空闲处理(内务处理,后台处理)
新的消息出现在消息队列中?
翻译字符消息
发送消息到窗口进程
图3-2消息泵执行后台处理并检查消息队列
消息泵调用的两个翻译函数是PreTranslateMessage()和:
:
TranslateMessage().目标窗口的MFC类可调用PreTranslateMessage在发送消息给它之前进行消息翻译,例如,CFrameWnd用PreTranslateMessage()将加速键(如,Ctrl+S存储文件)转换为命令消息.翻译前的消息通常被处理掉,而翻译后的消息(如果有的话)将被重新寄送到队列里.:
:
TranslateMessage是一个窗口函数,将原始键码转换为键字符.消息一旦被DispatchMessage()发送,MFC处理它就像处理SendMessage()发送的消息一样.
3.6MFC怎样处理一个接收到的消息
处理接收到的消息的目的非常简单:
将消息指向一个函数,该函数通过消息中的消息标识符处理它.非MFC窗口用简单的case语句来实现该目标,每个case语句执行一些函数,或调用其他一些函数.
MainWndProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam){switch(message){caseWM_CREATE:
:
:
:
下载
break;caseWM_PAINT:
:
:
:
break;default:
return(DefWindowProc(hWnd,message,wParam,lParam));}return(NULL);}
第3章消息处理
37
任何遗漏的消息将被传输到一个默认的消息处理函数,但是,case语句不能很好地适应C++和封装技术.在C++环境中,要求消息被一个专门处理该类型消息的类的成员函数处理.因此,MFC不采用case语句,而采用更加复杂和回旋的方法.但它允许用私有类处理消息,而只需做下面三件事情:
■从将要接收消息的CWnd类对象派生类(对于命令消息是CCmdTarget).■在派生类中写一个处理消息的成员函数.■在类中定义一个查找表(叫做消息映像),该表具有成员函数的条目和它要处理的消息的
标识符.然后,MFC依次调用下面的函数,指引输入消息到处理函数.1)AfxWndProc()接收消息,寻找消息所属的CWnd对象,然后调用AfxCallWndProc().2)AfxCallWndProc()存储消息(消息标识符和参数)供未来参考,然后调用WindowProc().3)WindowProc()发送消息给OnWndMsg(),然后,如果消息未被处理,则发送给DefWindowproc().4)OnWndMsg()要么为WM_COMMAND消息调用OnCommand(),要么为WM_NOTIFY消息调用OnNotify().任何被遗漏的消息都将是一个窗口消息.OnWndMsg()搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数.如果OnWndMsg()不能找到这样的处理函数,则把消息返回到WindowProc(),由它将消息发送给DefWindowProc().5)OnCommand()查看这是不是一个控件通知绝映射的消息,OnCommand()就调用OnCmdMsg().6)OnNotify()也试图将消息映射到制造通知的控件;如果映射不成功,OnNotify()就调用相同的OnCmdMsg()函数.7)根据接收消息的类,OnCmdMsg()将在一个称为命令传递(CommandRouting)的过程中潜在地传递命令消息和控件通知.例如,如果拥有该窗口的类是一个框架类,则命令和通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数.为什么要消息映像?
为什么要消息映像?
这毕竟是C++语言;为什么OnWndMsg()不为每个窗口消息调用一个预定义的虚拟函数?
因为它太占CPU.若是那样,当扫描一个消息映像以加速该过程时,OnWndMsg()可能会做出意想不到的事情,并陷入汇编器.注意通过重载WindowProc(),OnWndMsg(),OnCommand(),OnNotify()或(lParam不是NULL);如果它是,OnCommand()就试图将消息映射到制造通知的控件;如果它不是一个控件通知,或者控件拒
38
第一部分基础知识
下载
OnCmdMsg()可以修改这一过程.重载OnWndMsg()可以在窗口消息被排序之前插入该过程.重载OnCommand()或OnNotify()可以在消息被反射之前插入该过程.消息映射和命令传递将在下面部分进行详细讨论.现在,一步一步地跟踪窗口进程接收和解释一个消息.1.AfxWndProc()所有MFC窗口的窗口进程是:
LRESULTAfxWndProc(HWNDhWnd,UINTnMsg,WPARAMwParam,LPARAMlParam);
如果一个消息被发送,则SendMessage()本质上直接调用AfxWndProc();如果一个消息被寄送,则消息泵通过DispatchMessage()调用AfxWndProc().AfxWndProc()要做的第一件事是找到目标窗口的CWnd对象.虽然一个窗口不知道它的CWnd对象的任何情况,但应用程序在映像中跟踪窗口和类的配对.一旦CWnd对象被找到,AfxCallWndProc()就会被调用.2.AfxCallWndProc()AfxCallWndProc()的原型是:
LRESULTAfxCallWndProc(CWnd*pWnd,HWNDhWnd,UINTnMsg,WPARAMwParam,LPARAMlParam);
AfxCallWndProc()保存消息为将来引用.事实上,即使以后改变消息中的参数,当窗口进程用Default()成员函数进行默认处理时,窗口进程也将引用保存在这里的参数;一旦消息保存,WindowProc()就被调用.3.WindowProc()WindowProc()的原型函数为:
LRESULTWindowProc(UINTnMsg,WPARAMwParam,LPARAMlParam);
WindowProc()接着调用OnWndMsg(),它试图在类中为该消息寻找一个处理函数;任何返回到WindowProc()的未被处理的消息都被传输到DefWindowProc();DefWindowProc()是所有未被处理的消息的贮藏所.如果没有消息处理函数,并不意味着该消息不重要,例如,几乎不需处理WM_PAINT消息,但它是重要的,因为DefWindowProc()用该消息来绘制窗口的非客户区.注意MFC的CControlBar类(工具栏,对话条和状态栏的基类)重载WindowProc(),如
果任何一个下面所列的消息未经处理就传给了控制条,它们将被发送回它的父类(通常是主框架类).
WM_NOTIFYWM_DELETEITEMWM_COMMANDWM_COMPAREITEMWM_DRAWITEMWM_VKEYTOITEMWM_MEASUREITEMWM_CHARTOITEM
也可以在父窗口中自动地处理工具栏,状态栏或对话条的按钮.但是,应该尽可能地将这些功能封装到工具栏,状态栏或对话条类中.CMainFrame()类具有非常好的集成特性.4.OnWndMsg()OnWndMsg()的原型函数为:
BOOLOnWndMsg(UINTmessage,WPARAMwParam,LPARAMlParam,LRESULT*pResult);
下载
第3章消息处理
39
当消息为WM_COMMAND时,OnWndMsg()调用OnCommand();当消息是WM_NOTIFY时,则OnWndMsg()调用OnNotify().所有其他消息都被认为是窗口消息,并且OnWndMsg()搜索类中的消息映像以找到一个处理函数.消息映像OnWndMsg()通过搜索类和派生类中的消息映像为窗口消息寻找一个消息处理函数.消息映像是数据静态表,包含类中每个消息处理函数的一个条目.每个CWnd的派生类可以有一个消息映像(见图3-3).
CWnd2对象
③如果CWnd在该消息映像中不能找到一个消息处理函数,则在下一个派生CWnd的类中寻找
①两个类从CWnd派生
—CWnd1派生于
CWnd1对象
CWnd,CWnd2派生于CWnd1.每个派生类都有它自己的消息映像②当消息进入CWnd中时,首先在最顶层派生类的消息映像中寻找一个处理函数CWnd对象④如果CWnd找到一个消息处理函数,则用该消息调用它
图3-3OnWndMsg()搜索消息映像为窗口消息定位消息处理函数
每个消息映像都被括在两个宏之间
BEGIN_MESSAGE_MAP(CXxx,CYyy):
:
:
MessageMapentries:
:
:
END_MESSAGE_MAP
CXxx是该类的派生类名,CYyy是OnWndMsg()要搜索的下一个派生类名,通常CXxx派生于CYyy;在下一个类中的BEGIN_MESSAGE_MAP宏标识派生它的类,以此类推,直到所有类中的消息映像都被搜索为止.除了为OnWndMsg()标识消息映像的位置外,可以忽略这些宏是怎样工作的.每个消息映像条目使用下面的结构:
structAFX_MSGMAP_ENTRY{UINTnMessage;UINTnCode;UINTnI