MFC消息机制

作为MFC的一个初学者,在学习到消息机制的时候,一般会有几个问题,下面我将从一一展开。

何谓消息、消息处理、消息映射?

消息

消息简单的说就是指通过输入设备向程序发出指令要执行某个操作。主要指由用户操作而向应用程序发出的信息,也包括操作系统内部产生的消息。例如,单击鼠标左按钮,windows将产WM_LBUTTONDOWN消息,而释放鼠标左按钮将产生WM_LBUTTONUP消息,按下键盘上的字母键,将产生WM_CHAR消息。

消息处理

通过输入设备向程序发出指令要执行某个操作,具体的某个操作是你的一系列代码,也就是消息处理函数

在MFC中,每个专门的处理函数单独处理每个消息。消息处理函数通常是某一类的成员函数,编写消息处理函数是编写框架应用程序的主要任务。可以使用ClassWizard创建消息处事函数,然后从Classwizard直接跳到源文件消息处理函数,编写处理代码。

1、windows消息和控件通知的处理函数
    windows消息和控件通知都是由派生于CWnd的窗口类对象处理的。它们包括CFrameWnd、CMDIFrameWnd、 CMDIChildWnd、CView、CDialog以及从这些派生的用户自定义的类。这样的类对象封装了windows窗口句柄HWND。
    windows消息和控件通知都有默认的处理函数,这些函数在CWnd类中进行了预定义,MFC类库以消息名为基础形成这些处理函数的名称,这些处理函数的名称都以前缀"On"开始。有的处理函数不带参数,有的则有几个参数;有的还有除void以外的返回值类型。CWnd中消息处理函数的说明都有 afx_msg前缀。关键字afx_msg用于把处理函数和其他CWnd成员函数区分开来。例如,消息WM_PAINT的处理函数在CWnd中被声明成:afx_msg void OnPaint();
    windows消息常见的有鼠标消息(如WM_LBUTTONDOWN)消息)、键盘字符消息(WM_CHAR消息)、键盘按键消息(WM_KEYDOWN)、窗口重画消息WM_PAINT,水平和垂直条滚动消息WM_HSCROLL和WM_VSCROLL)以及系统时钟消息 WM_TIMER等。
2、命令消息的处理函数
    由于用户界面的对象是用户自已定义的,每个应用程序的用户界面对象千差万别,所以对用户界面对象的命令消息没有默认的处理函数。如果某条命令直接影响某个对象,则应该让这个对象来处理这条命令。例如FILE菜单上的Open命令当然与应用程序有关:应用程序打开一个特定的文档来响应该命令。所以,Open 命令的处理函数是应用程序类的一个成员函数。

    把命令消息映射成处理函数时,ClassWizard以命令ID来命名处理函数,可以接受、修改、或替换推荐使用的名字。例如,Edit菜单项的Cut命令,对应ID就是ID_EDIT_CUT,处理函数被命名成:afx_msg void OnEditCut();
    此外,对于控件按钮的BN_CLICKED通知消息,其处理函数可以被命名为:

afx_msg void OnClickedUseAsDefault();
  
    命令消息的处理函数没有参数值,也不返回值。

消息映射

SDK中消息其实非常容易理解,当窗口建立后便会有一个函数(窗口处理函数)开始执行一个消息循环,我们还可以清楚的看到消息处理的脉络。一个switch case语句就可以搞定,消息循环直到遇到WM_QUIT消息才会结束,其余的消息均被拦截后调用相应的处理函数。

但在封装了API的MFC中,消息似乎变的有些复杂了,我们看不到熟悉的switch case语句了,取而代之的是一个叫消息映射的东西。为什么MFC要引入消息映射机制,你可以想象一下,在现在的程序开发活动中,你的一个程序是否拥有多个窗体,主窗口就算只有一个,那菜单、工具条、控件这些都是子窗口,那我们需要写多少个switch case,并且还要为每个消息分配一个消息处理函数,这样做是多么的复杂呀。因此MFC采用了一种新的机制。利用一个数组,将窗口消息和相对应的消息处理函数进行映射,你可以理解成这是一个表,这种表就是消息映射表。这种机制就是消息映射张表在窗口基类CWnd定义,派生类的消息映射表如果你没有动作它是空的,也就是说如果你不手工的增加消息处理函数,则当派生窗口接受一个消息时会执行父类的消息处理函数。这样做显然是高效的。

例如,用户在按下鼠标左键时,Windows会发出WM_LBUTTONDOWN消息。MFC提供了一种消息映射机制将用户单击左键发出的WM_LBUTTONDOWN消息和一个函数联系在一起。具体代码为:

afx_msg void OnLButtonDown(UINT nFlags,CPoint point);  //语句的位置在头文件消息声明宏(DECLARE_MESSAGE_MAP)内
ON_WM_LBUTTONDOWN();        //这条语句的位置是在实现文件的消息宏(BEGIN_MESSAGE_MAP宏和END_MESSAGE_MAP之间)内
通过上面两行代码已经建立了消息的映射,用户只需要将所要实现功能的代码写入下面的函数内即可:
void CDlg::OnLuttonDown(UINT nFlags,CPoint point){
    //添加代码
    //CDlg::OnLuttonDown(nFlags,point);
}
MFC中每一个消息都对应一个函数,这样一对一的关系罗列出来就是一个表,如果开发人员需要处理某条消息,只要在消息对应的处理函数中写入代码即可。
消息映射表在MFC中主要是通过三个宏来实现的。这三个宏分别是:
DECLARE_MESSAGE_MAP消息的声明宏
BEGIN_MESSAGE_MAP宏
END_MESSAGE_MAP宏

消息映射宏

一般情况下,一个MFC的消息映射在程序中有三处相关信息:1)消息响应函数的声明 2)消息响应函数实现 3)用来关联消息和消息响应函数的宏
针对不同的 Windows 消息,MFC提供了不同的消息映射宏,主要分为如下六类:
Windows 消息映射宏  Windows 消息映射宏用于处理普通的窗口消息。此类消息映射宏前缀为“ON_WM_”,并且没有参数;因为它对应的消息和消息处理函数的函数名称、函数原型是确定的。 MFC提供了这类消息处理函数的定义和缺省实现。每个这样的宏处理不同的Windows消息。
例如:宏 ON_WM_CREATE()把消息WM_CREATE映射到OnCreate函数,消息映射条目的第一个成员nMessage指定为要处理的Windows消息的ID,第二个成员nCode指定为0。

命令消息映射宏  命令消息 WM_COMMAND 是一种特殊的窗口消息,它从一个窗口发送到另一个窗口,以处理来自用户的请求,是 ON_COMMAND 宏和 ON_COMMAND_RANGE 宏;这类宏带有参数,需要通过参数指定命令 ID和消息处理函数。
这些消息都映射到WM_COMMAND上,也就是将消息映射条目的第一个成员nMessage指定为WM_COMMAND,第二个成员nCode指定为CN_COMMAND(即0)。消息处理函数的原型是void (void),不带参数,不返回值。
除了单条命令消息的映射,还有把一定范围的命令消息映射到一个消息处理函数的映射宏 ON_COMMAND_RANGE。这类宏带有参数,需要指定命令ID的范围和消息处理函数。这些消息都映射到WM_COMMAND上,也就是将消息映射条目的第一个成员nMessage指定为WM_COMMAND,第二个成员nCode指定为CN_COMMAND(即0),第三个成员nID和第四个成员nLastID指定了映射消息的起止范围。消息处理函数的原型是void (UINT),有一个UINT类型的参数,表示要处理的命令消息ID,不返回值。
控制通知消息映射宏  控制通知消息是指控件窗口发送到其父窗口的消息,其消息映射宏为 ON_CONTROL 和 ON_CONTROL_RANGE,有时在程序中并不见 ON_CONTROL 宏,而见 ON_BN_CLICKED 宏, 其实 ON_CONTROL 宏派生出许多的映射宏,包括 ON_BN_CLICKED 宏等;
控件通知消息映射宏 控件通知消息映射宏处理的窗口消息有 WM_COMMAND,WM_NOTIFY,其消息映射宏有 ON_NOTIFY 和 ON_NOTIFY_RANGE;
反射消息映射宏 反射消息是指子窗口向父窗口发送的通知消息或控件通知消息,父窗口将该消息转化为相应的反射消息发送给子窗口优先处理。 处理窗口消息 WM_NOTIFY+WM_REFLECT_BASE 的宏是 ON_NOTIFY_REFLECT,处理窗口消息 WM_COMMAND+WM_REFLECT_BASE 的宏是 ON_CONTROL_REFLECT, 还有其他的反射消息宏,具体请参考MDSN。
扩展消息映射宏、用于其他消息的宏  
很多普通消息映射宏都有对应的扩展消息映射宏,例如:ON_COMMAND对应的ON_COMMAND_EX,ON_ONTIFY对应的ON_ONTIFY_EX,等等。
其他消息的宏,例如用于用户定义消息的 ON_MESSAGE。这类宏带有参数,需要指定消息ID和消息处理函数。消息映射条目的第一个成员nMessage被指定为消息ID,第二个成员nCode被指定为0,第三个成员nID和第四个成员也是0。消息处理的。
消息映射宏中有带有COMMAND字样的都是用来处理命令消息的。命令消息主要是通过单击菜单及单击按钮产生的。命令消息在CCmdTarget类是没有处理函数的,也就是说不在消息映射表内。菜单项和按钮还有其他一些控件都有其ID值,命令消息映射宏主要就是将菜单、按钮和其他一些控件的ID和处理函数映射起来。命令消息的添加也是通过MFCClassWizard来完的。

Windows消息组成结构

windows消息由消息号和参数组成:
消息号:windows操作系统通过32整数标识一条windows消息,称为消息号。
windows消息号具有三个特点:
1) 唯一性:唯一性指对于windows操作系统中的每一条消息都有一个且只有一个消息号与它相对应。由于windows操作系统通过消息激活相应的过程,因此消息的标识必须是唯一的。
2) 直观性:消息号在形式上表现为消息名,消息名往住直观地说明了消息的产生方式和类型,例如消息名WM_LBUTTONDOWN直观地表示了单击产生的消息。
3) 可自定义性:windows系统定义了一些消息号和消息名,程序员可以运用这些消息,也可以使用自已定义的窗口的消息。形式如下: #define 消息名 消息号
注意,为了不与现有的windows系统消息相冲突,在消息号的定义中通常采用如下:
WM_USER+n   WM_USER是windows操作系统定义的用户消息起始值,程序员必须从该起始值后选取消息号,例如自定义消息名为“MESSAGE_1”和 “MESSAGE_2”,可采用如下形式:

#define MESSAGE_1 WM_USER+1
#DEFINE MESSAGE_2 WM_USER+2
参数:windows的消息具有以下两个参数:字参数(wParam);长参数(lParam)。字参数和长参数都是32位整数,用于提供消息的附带消息,是消息传递过程中参数的载体。附加信息的消息号取决于消息号。

MFC提供的消息结构

同时MFC定义了下面的两个主要结构:

AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY{
    UINT nMessage;   // Windows消息的ID号
    UINT nCode;  // 控制消息的通知
    UINT nID;    // Windows控制消息的ID
    UINT nLastID;   //表示是一个指定范围的消息被映射的范围
    UINT nSig;  //表示消息的动作标识
    AFX_PMSG pfn;    // 指向消息处理函数的指针
};

AFX_MSGMAP
struct AFX_MSGMAP{
#ifdef _AFXDLL
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
    const AFX_MSGMAP* pBaseMap;
#endif
    const AFX_MSGMAP_ENTRY* lpEntries;
};
///AFX_MSGMAP可以得到基类的消息映射入口地址和得到本身的消息映射入口地址。

MFC到底有哪些消息?

windows消息的背景

    在Windows3.1里,控件会将mouse,   keybord等等的消息通知它的父窗口,使用的消息就只有WM_COMMAND,事件种类和控件ID被包含在wParam中,控件的句柄包含在lParam中。由于wParam和lParam已经满了,当控件要向父窗口发送其它特殊消息同时附带很多信息的时候就没有地方可以存放它们了。所以Windows3.1中定义了许多其它的消息种类,比如WM_VSCROLL,WM_CTLCOLOR等等,每种消息wParam,lParam中附带的信息是不同的。   
    当到了Win32后,控件的种类越来越多,当然不可以为每一个控件都定义一套消息,这样也不利于系统的扩充。所以在Win32中定义了唯一一个强大的消息WM_NOTIFY。当然WM_NOTIFY也遵守原来的消息规则,既只带参数wParam和lParam。唯一不同处在于,此时的lParam中传送的是一个NMHDR指针。不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大,这个可以看看MSDN中的相关说明,TreeControl中就有很多这种消息。       
现在就可以知道为什么有ON_MESSAGE,ON_COMMAND,ON_NOTIFY了。   
ON_MESSAGE是处理所有的Windows的消息的,因为所有的消息都以相同的格式传送,也就是ID,   WPARAM,   LPARAM.   
ON_COMMAND是专门处理WM_COMMAND消息的,这样我们就不用自己解开WM_COMMAND中wParam和lParam中传送的控件ID,事件种类…,所有的都在MFC内部解决了,当然方便了。   
ON_NOTIFY更是不用说了,看看他的处理函数,是不是把NMHDR解出来了。
这样一样就一目了然了,ON_COMMAND和ON_NOTIFY都可以用ON_MESSAGE来处理,只不过自己要多做很多事情。ON_COMMAND和ON_NOTIFY最好就不要互换了!


【注:此处也可参考博文http://blog.csdn.net/arnozhang12/article/details/8051826,以便理解】

消息类别

其实消息类别可以分成多种,有三种主要的消息类别:
1、Windows 消息

此类消息主要包括以前缀 WM_ 开头的消息,WM_COMMAND 除外Windows 消息由窗口和视图处理其所对应的宏一般为在消息 WM_ 前面加上 ON_。此类消息往往带有用于确定如何处理消息的参数。

2、控件通知

此类消息包括从控件和其他子窗口发送到其父窗口的 WM_COMMAND 通知消息。例如,当用户在编辑控件 (Edit Control) 中执行可能更改文本的操作后,该编辑控件 (Edit Control) 将向其父级发送包含 EN_CHANGE 控件通知代码的 WM_COMMAND 消息。该消息的窗口处理程序以某种适当的方式响应此通知消息,例如在控件中检索该文本。
框架像传送其他 WM_ 消息一样传送控件通知消息。但是有一个例外的情况,即当用户单击按钮时由按钮发送的 BN_CLICKED 控件通知消息。该消息被作为命令消息特别处理,并像其他命令一样传送。
3、命令消息

此类消息包括用户界面对象(菜单、工具栏按钮和快捷键)发出的 WM_COMMAND和 CN_UPDATE_COMMAND_UI通知消息。框架处理命令的方式与处理其他消息不同,可以使用更多种类的对象处理命令。
Windows 消息和控件通知消息由窗口来处理(窗口是从 CWnd 类派生的类的对象)。包括 CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog 以及从这些基类派生的您自己的类。这些对象封装了 HWND——Windows 窗口的句柄。

命令消息可以由范围更广的对象(文档、文档模板以及应用程序对象本身)处理,而不仅仅由窗口和视图处理。当某一命令直接影响到某个特定对象时,应当让该对象处理此命令。例如,“文件”菜单中的“打开”命令在逻辑上与应用程序相关联:该应用程序接收到此命令时会打开指定的文档。因此“打开”命令的处理程序是应用程序类的成员函数。
命令消息我们比较常见的便是菜单项工具条了,大家可以看到他的消息映射宏和窗口消息不太一样,一般的形式是这样的

第一个参数是命令ID,一个ID号对应一个消息处理,当然你可以让多个ID共用一个处理函数。常见的应用例如:菜单项打开文档的ID和工具条按钮打开文档的ID同时使用一个处理函数,或者直接将它们的ID设成相同的。

ON_COMMAND(id,memberFxn)  // 标准的命令消息
ON_COMMAND_EX(ID,pfn)   // 多个对象对同一命令ID的处理
ON_COMMAND_RANGE(nID,nLastID,pfn)  // 多个命令ID提供相同的处理 
ON_UPDATE_COMMAND_UI(ID,pfn) 
ON_UPDATE_COMMAND_UI_RANGE(nID,nLastID,pfn)  // 该函数可以处理一组[命令用户接口对象]的外观

还有一种消息叫通知消息。例如树型控件的等一些复杂的控件在单击后需要传递更多的信息,例如光标的位置和当前项的一个结构,所以MFC为控件的每个通知消息也定义了一个宏,它长成了这个样子:

ON_CONTROL(EN_CHANGE,id,memberFxn)

还有很多种消息存在于MFC,宏定义有区别,大家可以触类旁通。
窗口消息有上百个。你可以从MSDN上查到WM_开头的,或者查看CWnd的成员函数,会给你列出很多,别忘了还有很多非窗口消息。

此外,按消息的产生可以分为四类:

  (1)外界输入事件产生的消息; 
       (2)windows系统向应用程序发出的消息
  (3)应用程序之间发送的消息; 
       (4)其他

根据产生消息的对象,可以进一步将消息分为以下几类:
  (1)窗口管理消息  包括激活基本窗口操作的消息,如关闭、最大化等。
  (2)初始化消息  应用程序、窗口、控件等对象初始化的消息。
  (3)输入消息  包括键盘、鼠标、绘图仪等处部输入工具的输入消息。
  (4)系统消息  windows系统内部消息。
  (5)剪贴板消息  操作剪贴板时产生的消息。
  (6)系统信息消息  用于系统信息发送和处理的消息。
  (7)控件处理消息  操作控件时产生的消息。
  (8)控件通知消息  系统采用空件通知消息向控件发送消息。
  (9)滚动条消息  操作滚动条时产生的消息
  (10)非用户区消息  非用户区产生的消息
  (11)MDI消息  用于多文档操作的消息
  (12)DDE消息  用于动态数据交换的消息。
  (13)应用程序自定义的消息等等。

windows消息由消息号和参数组成:

MFC消息处理的过程是怎样的?

1. _AfxCbtFilterHook截获消息(这是一个钩子函数)

2. _AfxCbtFilterHook把窗口过程设定为AfxWndProc。

3. AfxWndProc()接收消息,寻找消息所属的CWnd对象,然后调用AfxCallWndProc( )。
4. AfxCallWndProc()存储消息(消息标识符和消息参数)供未来参考,然后调用WindowProc( )。
5. WindowProc()发送消息给OnWndMsg( ),如果消息未被处理,则发送给DefWindowproc( )。
6. OnWndMsg()首先按字节对消息进行排序,对于WM_COMMAND消息,调用OnCommand()消息响应函数;对于WM_NOTIFY消息调用OnNotify()消息响应函数。任何被遗漏的消息将是标准消息。OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,由它将消息发送给DefWindowProc()函数。
7. OnCommand()查看这是不是一个控件通知(lParam参数不为NULL),如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;如果它不是一个控件通知,或者控件拒绝映射的消息,OnCommand()就会调用OnCmdMsg()函数。
8. OnNotify( )也试图将消息映射到制造通知的控件;如果映射不成功,OnNotify( )就调用相同的OnCmdMsg( )函数。
9. 根据接收消息的类,OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。例如:如果拥有该窗口的类是一个框架类,则命令和控件通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数。

如何添加MFC消息?

我们已经了解了WINDOW的消息机制,如何加入我们自己的消息呢?
一个标准的消息处理程序是这个样子的:
在 CWnd 类中预定义了标准 Windows 消息(WM_XXXX  WM是WINDOW MESSAGE的缩写) 的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT 消息的处理程序在 CWnd 中被声明为:

afx_msg void OnPaint();

afx_msg 关键字通过使这些处理程序区别于其他 CWnd 成员函数来表明 C++ virtual 关键字的作用 。但是请注意, 这些函数实际上并不是虚拟的,而是通过消息映射实现的 。我们在本文的一开始便说明了为什么要这样做。
所有能够进行消息处理的类都是基于CCmdTarget类的 ,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。

若要重写基类中定义的处理程序,只需在派生类中定义一个具有相同原型的函数 ,并创建此处理程序的消息映射项 。我们通过ClassWizard可以建立大多数窗口消息或自定义的消息,通过ClassWizard可以自动建立消息映射,和消息处理函数的框架,我们只需要把我们要做的事情填空,添加你要做的事情到处理函数。这个非常简单,就不细说了。

但是也许我们需要添加一些ClassWizard不支持的窗口消息或自定义消息,那么就需要我们亲自动手建立消息映射和消息处理的框架,通常步骤如下:
第一步:定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。

#define WM_MYMESSAGE (WM_USER + 100)

第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。

LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
    // TODO: 处理用户自定义消息,填空就是要填到这里。
    return 0;
}

第三步:在类头文件的AFX_MSG块中说明消息处理函数:

// {{AFX_MSG(CMainFrame)
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中

ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )

可以看出,用户自定义的消息和我们通过ClassWizard添加的消息一样,都是利用了ON_MESSAGE宏,建立的消息映射 




参考

MFC的消息机制的实现原理和消息处理的过程 http://blog.csdn.net/cherish_2012/article/details/21182265

WM_COMMAND 和 WM_NOTIFY 的区别 http://blog.csdn.net/arnozhang12/article/details/8051826

MFC-消息分派 http://blog.csdn.net/linzhengqun/article/details/1905671




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值