MFC的消息处理模式

MFC的消息处理模式

在Windows中发生的一切都可以用消息来表示,消息用于告诉操作系统发生了什么,所有的Windows应用程序都是消息驱动的,消息机制是Windows应用程序的核心。

在Windows中,不同的消息由应用程序的不同部分进行处理。MFC库将很多底层的消息都屏蔽了,使用户更加方便、简易地处理消息。例如,用户接收到诸如移动鼠标键(WM_MOUSEMOVE)消息或单击鼠标键(WM_LRBUTTONDOWN)消息时不必处理窗口和鼠标的重画工作,MFC及应用程序框架会替用户做这些工作。在使用MFC进行编程时,用户只需处理一些高层的消息,例如,“用户在单击窗口中的OK按扭”,“用户现在选中了下拉列表框中的第五项”等等,这样就大大减轻了程序员的负担。

一个消息是由消息的名称(UINT)和两个参数(WPARAM, LPARAM)组成。消息的参数中包含有重要的信息。例如对鼠标消息而言,LPARAM中一般包含鼠标的位置信息,而WPARAM参数中包含了发生该消息时,SHIFT、CTRL等键的状态信息,对于不同的消息类型来说,两个参数也都相应地具有明确意义。

消息与输入焦点

Windows是一个以消息为导向的系统,应用程序只能被动地等待用户按键的消息,不能主动地去读键盘的状态,也就是说,每当键盘上有个键被按下,系统就会发出一个按键消息给窗口,告诉它某个键被按下去了,只要鼠标移动一下,系统也会发出相应的消息,并把鼠标的坐标信息传给窗口。

Windows可以同时执行许多程序,但键盘只有一个,怎么判断由哪个窗口接收键盘及鼠标的消息呢?采用“输入焦点”(inpuut focus)技术可以解决这个问题。只要某个窗口取得输入焦点,它不但会被提升到屏幕的最前面,颜色也会有所不同,所有的键盘消息就会导向该窗口,该窗口也成为“活动窗口”。

窗口如何取得输入焦点?通常被鼠标单击的窗口会得到输入焦点,除此之外,程序本身也可以利用SetFocus()来指定哪个窗口拥有输入焦点。

CWnd*CWnd::SetFocus();

如果调用某窗口的SetFocus()成员函数,该窗口就可以取得输入焦点,该函数返回前一个拥有输入焦点的窗口。

如果某个窗口的输入焦点被抢走,Windows系统就会发出WM_KILLFOCUS消息给这个失去输入焦点的窗口,同时还会告诉该窗口下一个取得输入焦点的窗口的指针。而获得输入焦点的窗口则会收到WM_SETFOCUS消息。

消息响应函数分别为:

afx_msgvoid OnKillFocus(CWnd* pNewWnd);

其中的参数为得到输入焦点的窗口的指针。

Afx_msgvoid OnSetFocus(CWnd* pOldWnd);

其中的参数为失去输入焦点的窗口的指针。

 

消息的分类

Windows系统预定义了许多消息,每个消息都拥有一个宏定义,即用形象的字符串来标识消息,一系列#define 语句将消息与特定数值联系起来,可以在头文件WinUser.h中找到这些宏定义,例如

#defineWM_PAINT 120

可以在程序中通过消息名“WM_PAINT”来访问它。其他消息如:

#define WM_MOUSEMOVE                    0x0200

#defineWM_LBUTTONDOWN                  0x0201

#define WM_LBUTTONUP                    0x0202

#defineWM_LBUTTONDBLCLK                0x0203

#defineWM_RBUTTONDOWN                  0x0204

#define WM_RBUTTONUP                    0x0205

#defineWM_RBUTTONDBLCLK                0x0206

#defineWM_MBUTTONDOWN                  0x0207

#define WM_MBUTTONUP                    0x0208

#defineWM_MBUTTONDBLCLK                0x0209

系统定义的消息有不同的前缀,不同的前缀有不同的含义。

1. 标准的Windows消息

除了WM_COMMAND消息,所有以WM_为前缀的消息都是标准的Windows消息,如窗口、鼠标移动、窗口大小改变等,程序启动或退出甚至每一段固定的时间都会产生标准Windows消息。如

1)    键盘消息

对于窗口而言,来自用户的按键输入可分为两类,一类是系统键(system key),另一类则是非系统键。凡是ALT和其它键一同按下的组合称为“系统键”,窗口收到系统键之后,会自动地将它解释成系统事件,或者查阅键盘加速表,将系统键翻译成加速表指定的信息。如:ALT+F4的组合会迫使窗口关闭,“ALT+字母”的组合可能会拉下某个菜单。

当用户按下某个键时,Windows系统会先发出WM_KEYDOWN消息给窗口,这个消息的意思是“按键被压下去”。接着Windows系统会发出WM_CHAR给同一个窗口,这个消息代表的意义是“系统送来某个字符”,如果用户放开此键,Windows系统会发出WM_KEYUP消息,表示“按键被放开”。如果用户一直按住某个键不放,经过一段时间之后会产生“连发”的效果,造成Windows系统不停地发出WM_KEYDOWN与WM_CHAR消息。

计算机内部以ASCII码的规则来记录所有的英文字母和数字符号。不过不是键盘上每个按键都可以对应成ASCII码中的字符,如大小写键、CTRL键、F1到F12键等。

每个按键都有对应的扫描码,PC BIOS收到键盘的中断消息后,会自动将扫描码翻译成ASCII码,但有些控制键无法译成ASCII码,如Page UP、Page Down等。Windows定义了一套与硬件无关的“虚拟键码”来表示键盘上所有的按键,如A键就是VK_A、ESC键就是VK_ESC、F1键是VK_F1、ALT键是VK_MENU等。因为“虚拟键码”定义的规则与硬件无关,所以有些虚拟键在通常的键盘上根本就找不着。

#defineVK_LBUTTON        0x01

#defineVK_RBUTTON        0x02

#defineVK_CANCEL         0x03

#defineVK_MBUTTON        0x04    /* NOT contiguous with L & RBUTTON */

 

#defineVK_BACK           0x08

#defineVK_TAB            0x09

 

#defineVK_CLEAR          0x0C

#defineVK_RETURN         0x0D

 

#defineVK_SHIFT          0x10

#defineVK_CONTROL        0x11

#defineVK_MENU           0x12

#defineVK_PAUSE          0x13

#defineVK_CAPITAL        0x14

 

#defineVK_F1             0x70

#defineVK_F2             0x71

#defineVK_F3             0x72

#defineVK_F4             0x73

#defineVK_F5             0x74

#defineVK_F6             0x75

#defineVK_F7             0x76

#defineVK_F8             0x77

#defineVK_F9             0x78

#defineVK_F10            0x79

#

#defineWM_CHAR               0x0102 //字符消息

WM_CHAR也称为键盘消息,如果某窗口拥有输入焦点,当用户在应用程序运行时按下一个键时,系统就会产生一个键盘消息WM_CHAR,告诉此窗口键盘上哪个键被按下了。该消息的处理函数为OnChar()。具体形式为:

afx_msgvoid OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

各参数含义为:

nChar:键盘所输入的ASCII码。

nRepCnt:按键的重复次数,当用户按下某个键不放时,该参数将持续增加。

nFlag:用于传递按键的其它一些信息,如扫描码,上一次按键状态等。具体如下:

字节

说明

0-7

键盘扫描码

8

此按键为扩充按键,如F1,F12等功能键,此字节等于1时为真

9-12

保留

13

此字节为1表示按下键的同时,ALT键也被按住了

14

前一个按键状态。此字节为1代表信息在按键被按下之前就送出来了

15

此字节为1表示这个按键已经被放开了,反之就表示还被按着

 

此外还有两个常用的键盘消息:WM_KEYDOWN和WM_KEYUP.

WM_KEYDOWN消息是当用户按下一个非系统键时产生的,非系统键就是不按下ALT键时的按键。

WM_KEYUP消息是当用户释放一个非系统键时产生的。

2) 鼠标消息

① #define WM_MOUSEMOVE       0x0200  //鼠标移动消息

当鼠标在某个窗口内移动时,Windows会不断地发出鼠标移动消息WM_MOUSEMOVE,并把鼠标的最新位置传给该窗口。如果在窗口的范围内按下鼠标左键,系统就会发出“按下左键”的WM_LBUTTONDOWN消息给该窗口,等到用户放开按键后,再发出“放开左键”的WM_LBUTTONUP消息给该窗口。

鼠标移动消息的消息响应函数为:

afx_msg void OnMouseMove(UINT nFlags, CPoint point)

其中的参数含义如下:

UINT nFlag:此事件发生时,鼠标按键、键盘控制键的状态,可以是以下值的任意组合:

当用户按下CTRL键时,nFlags设置为MK_CONTROL。

当用户按下鼠标左键时,nFlags设置为MK_LBUTTON。

当用户按下鼠标中键时,nFlags设置为MK_MBUTTON。

当用户按下鼠标右键时,nFlags设置为MK_RBUTTON。

当用户按下SHIFT键时,nFlags设置为MK_SHIFT。

    CPoint point:该参数为一个CPoint结构,用于指示当前鼠标光标所在的x, y坐标。

 

鼠标除了移动会产生消息之外,鼠标上面的按键也会象键盘按键一样产生消息,以左键为例,当它被压下去、按住未放开时,Windows会发出WM_LBUTTONDOWN消息;当该键被放开后,Windows会发出WM_LBUTTONUP消息;当鼠标左键双击之后,Windows会发出WM_LBUTTONDBLCLK消息;

#defineWM_LBUTTONDOWN    0x0201  //鼠标左键按下消息

当用户在窗口客户区中按下鼠标左键时,产生WM_LBUTTONDOWN消息,该消息的处理函数为:OnLButtonDown(),它带有两个参数。

afx_msg void OnLButtonDown(UINT nFlags, CPoint point)

 

#defineWM_LBUTTONUP         0x0202 //鼠标左键抬起消息

消息处理函数为:

afx_msg void OnLButtonUP(UINT nFlags, CPoint point)

 

#defineWM_LBUTTONBLCLK     0x0203 //双击鼠标左键消息

消息处理函数为:

afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point)

 

其它鼠标中键、右键与此类似。

 

标准的Windows消息由窗口和视图处理,即CWnd和它的派生类都可以接收标准Windows消息。这类消息中通常含有如何对消息进行处理的一些参数。标准的Windows消息都有默认的处理函数,这些处理函数在CWnd类中进行了预定义。

2. 控件通知消息

控件通知消息是由编辑框、列表框等控件或子窗口产生并传向父窗口的消息,它以WM_COMMAND为消息名,在消息的参数中包含有具体的控件通知代码,以区别具体的控件通知消息。

控件通知消息必须由窗口类的对象,即直接或间接由CWnd类或其派生类的对象进行处理。比如主框架窗口类、子边框窗口类或窗口类等。

不同的控件产生的通知消息有不同的前缀,如:

1)文本控件消息

文本控件消息以EM_为前缀,如:

#define EM_GETSEL      0X00B0       //获得选择文本

#define EM_SETSEL      0X00B1       //设置选择文本

#define EM_GETRECT    0X00B2      //获得文本区域

#define EM_SETRECT     0X00B2      //设置文本区域

2按扭控件消息

按扭控件消息以BM_为前缀,如

#define BM_GETCHECK  0X00F0   //确定单选按扭或复选按扭是否被选中

#define BM_SETCHECK  0X00F1   //设置按扭控件的选中标记

#define BM_GETSTATE  0X00F2   //获得按扭控件的状态

#define BM_SETSTATE  0X00F3   //设置按扭显示的高亮状态

#define BM_SETSTYLE  0X00F4   //设置按扭控件的风格

#define BM_CLICK      0X00F5   //单击按扭控件

3)列表框控件消息

列表框控件消息以LB_为前缀

#define LB_ADDSTRING  0x0180   //向列表框中加入字符串

#define LB_INSERTSTRING   0x0181   //向列表框的指定位置中加入字符串

#define LB_GETTEXT       0x0189   //获取列表框中的文本

4组合框控件消息

组合框控件消息以CB_为前缀

#define CB_ADDSTRING     0x0143   //把字符串加入到组合框的列表部分

#define CB_DELETESTRING   0x0144   //从组合框列表部分删除字符串

5滚动栏控件消息

滚动栏控件消息以SBM_为前缀

#define SBM_SETPOS     0x00E0   //设置滚动栏滑块的位置

#define SBM_GETPOS     0x00E1   //获取滚动栏滑块的位置

#define SBM_SETRANGE  0x00E2   //设置滚动栏滑块的范围

6默认下压式按扭消息

#define DM_GETDEFID   (WM_USER+0)  //获取对话框默认按扭控件的ID

#define DM_SETDEFID   (WM_USER+0)  //改变对话框默认按扭控件的ID

 

3. 命令消息

命令消息是一类特殊的消息。每当用户选择一个菜单项、单击一个按钮、工具栏和加速键时,就发出一个WM_COMMAND命令消息。

所有命令消息都包含一个共同的参数,那就是该命令消息需要操作的资源ID值。例如当我们单击菜单项File|New时,产生的命令消息中将包含该菜单项的资源ID值,如ID_FILE_NEW。

命令消息的处理与前两种消息不同,命令消息可以被更广泛的对象处理,而不仅仅局限于窗口类。所有派生自CCmdTarget的类,如文档类、文档模板类和应用程序类等都可以处理命令消息。

命令消息的处理函数为:OnCommand()。

4. 消息标志符取值范围

系统定义的消息            0x0000到0x03FF

用户定义内部消息          0x4000到0x07FF

系统定义的消息            0x8000到0x0BFF

用户定义外部消息          0xC000到0xFFFF

 

下面是几个重要的与窗口操作有关的消息:

1. WM_CREATE消息

当一个应用程序调用CreateWindow函数创建一个窗口时,会产生WM_CREATE消息,该消息的处理函数为:

afx_msgint OnCreate(LPCREATESTRUCT lpCreateStruct)

 

2. WM_PAINT消息

当Windows或另一个应用程序要求重画当前应用程序中的窗口或部分窗口时,会发出WM_PAINT消息。通常在调用UpdateWindow函数或RedrawWindow()之后会产生WM_PAINT消息。

该消息的处理函数为:OnPaint()。该函数没有参数。

在Windows编程中与窗口相关的消息还有很多,如WM_HSCROLL、WM_VSCROLL等。

 

定时消息

定时消息也是Windows中的一类重要的消息。当用户希望应用程序每隔一个特定的时间间隔执行某项指定操作时,就需要用到定时消息WM_TIMER。

在进行定时操作时,用户需要先调用SetTimer函数来创建一个定时器,并设置该定时器的事件标志nIDEvent以及时间间隔uElapse。然后编写WM_TIMER消息的处理函数OnTimer()。此函数拥有一个参数,即定时器的事件标志nIDEvent,用于指定使用哪个定时器。用户可以在OnTimer()函数中编写定时操作的程序代码。

当应用程序开始运行后,每过uElapse毫秒,就发出一个WM_TIMER消息,从而进入OnTimer()函数进行处理。

如  SetTimer(ID_TIMER, 1000, NULL); //该定时器ID,时间间隔,调用函数

 

 

所有的消息处理函数原型前面都有关键字 afx_msg前缀,用于把消息处理函数与其它函数区分开来。

 

消息映射

以前的程序员编写Windows程序时,需要使用WinMain()例程来向WndProc函数发送消息,对消息的处理是通过switch-case结构实现的,当要处理的消息很多时,switch-case结构的分支很多,影响程序的可读性。

在编写MFC应用程序时,不再采用这种古老的方法,而是采用一种相当巧妙的方法,通过一些宏将特定的消息映射到派生类中相应的成员函数上,这种方法被称作消息映射机制。所谓消息映射,就是将消息与其处理函数相对应,即当系统产生一条消息时,它能够找到处理该消息的函数。

在MFC中,凡是从CcmdTarget(命令发送类)派生的类都可以有消息映射,如果要建立消息映射,需要进行以下操作:

1 在类的相应头文件(.H)中申明消息映射表,它通常情况下被写在一个类定义的最后。

DECLARE_MESSAGE_MAP()

2 在类的实现代码文件(.cpp)中加入消息映射表,如下所示:

  BEGIN_MESSAGE_MAP(CfirstAppApp, CWinApp)

    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

    ON_COMMAND(ID_FILE_NEW, CwinApp::OnFileNew)

    ON_COMMAND(ID_FILE_OPEN,CwinApp::OnFileOpen)

    ON_COMMAND(ID_FILE_PRINT_SETUP,CwinApp::OnFilePrintSetup)

 END_MESSAGE_MAP()

 

VC++中提供了三种消息映射宏:DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP和END_MESSAGE_MAP。DECLARE_MESSAGE_MAP用于类申明的末尾,即在.h文件中;在实现文件(.cpp)中,使用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP来完成消息映射的任务。这两个宏总是配合使用。在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏之间列出了消息映射的各个入口。

DECLARE_MESSAGE_MAP:用于申明在源文件中存在消息映射。

BEGIN_MESSAGE_MAP:用于标志消息映射的开始。

END_MESSAGE_MAP:标志消息映射的结束。

ON_COMMAND:用于将一个特定的命令消息映射到类的成员函数,即使用该成员函数来处理该命令消息。即指定命令消息和它的处理函数名称。

ON_COMMAND_UPDATE_UI:用于将一个特定的更新命令消息映射到类的成员函数。例如菜单的灰化、命令按钮的禁止等。

其中,BEGIN_MESSAGE_MAP宏有两个参数,第一个参数指出该消息映射所属类的名称,第二个参数指出前一个类的直接基类。

当使用ClassWizard创建新类时,该向导为新类提供一个消息映射,但向导支持的映射宏是有限的,有时需要用户手动创建消息映射。

 

消息传递

 

Windows应用程序中的大多数消息都是用户与应用程序相互作用而产生的。当产生消息时,系统使用CwinApp类的Run函数来检索消息,并把消息发送到适当的窗口。在MFC程序中,命令消息与非命令消息采用的是两种不同的传递方式。

非命令消息的传递

非命令消息(即标准Windows消息和控件通知消息)必须由窗口类的对象,即直接或间接由CWnd类或其派生类的对象进行处理。这个窗口可能是主框架窗口、标准控件、对话框、视图或其它类型的子窗口。在程序运行过程中,每个Windows窗口都与窗口对象联系在一起。每个窗口对象都有自己的消息映射和处理函数。MFC利用消息映射把消息与其对应的处理函数相匹配。

命令消息的传递

命令消息的处理与其它消息不同,命令消息可以被更广泛的对象处理,而不仅仅局限于窗口类。所有派生自CCmdTarget的类,如文档类、文档模板类和应用程序类等都可以处理命令消息。当某个命令消息产生后,它被应用程序框架沿着命令消息的传递路径发送。命令消息的传递路径见下表。传递路径上的每一个对象都按顺序检查自己的消息映射,看看是否能够处理这个命令消息。如果消息与处理函数相匹配,则调用处理函数处理该消息并不再搜索后面的对象,不然就继续搜索下去。

若程序为SDI应用程序,则传递路径从第二栏开始。

从表中可以看出,消息的传递尊循一定的规则:一个对象接收到命令消息后,先发给当前活动的子对象,然后交给自己处理,如果都不能处理,最后再发给其它对象。

命令消息的传递路径

收到消息的对象及其所属类

传递路径

MDI主框架窗口对象

(CMDIFrameWnd及其派生类)

活动的MDI子边框窗口(CMDIChildWnd)

主框架窗口,即其自身

应用程序(CWinApp)对象

文档边框窗口对象

(CMDIChildWnd或CframeWnd及其派生类)

活动视图

文档边框窗口,即其自身

应用程序(CWinApp)对象

视图对象

(Cview及其派生类)

视图

与视图关联的文档

文档对象

(CDocument及其派生类)

文档

与文档关联的文档模板

对话框对象

(CDialog及其派生类)

对话框

拥有此对话框的窗口

应用程序(CWinApp)对象

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值