MFC 的消息机制

    MFC 的消息机制

    众所周知,windows是基于消息驱动的,作好消息处理是WINDOWS编程的关键任务之一,用VC制作WINDOWS程式同样离不开消息的处理。 这就要求我们对 VC中消息的处理有一个比较清淅的认识。只有这样才可能在必要的时候亲自动手完成一些复杂的消息映射处理。

    在MFC中消息是 通过一种的消息映射机制来处理的。其实质是一张消息及其处理函数的一一对应表以及分析处理这张表的应用框架内部的一些程序代码.这样的好处是可以避免像早 期的SDK编程一样需要罗列一大堆的CASE语句来处理各种消息。由于不同种类的消息其处理方法是不同的,所以我们有必要先弄清楚 WINDOWS消息的种类。

    WINDOWS 消息的种类:

    1、标准WINDOWS 消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等。

    2、命令消息:命令消息以 WM_COMMAND为消息名。在消息中含有命令的标志符ID,以区分具体的命令。由菜单,工具栏等命令接口对象产生。

    3、控件通知消息:控 件通知消息也是以WM_COMMAND为消息名。由编辑框、列表框和子窗口发送给父窗口的通知消息。在消息中包含控件通知码,以区分具体控件的通知消息。

    其中从CWnd派生的类可以接受上面的三种消息,从CCmdTarget派生的类能够接收命令消息。在使用MFCAppWizard创建的应用程序中, 消息传递的机制是:

    App 类与Doc类都是从CCmdTarget类派生而来,所以只能接收命令消息(如单击菜单产生的消息),View类与Frame框架类都是从CWnd类派生 而来,所以能够接收上面的三种消息。对于以上三种消息,期响应的顺序是这样的:首先由框架类接收到该消息,框架类将该消息递交给其子窗口View,如果 View没有对该消息进行响应,则由View递交给Doc,如果Doc也没有对该消息进行响应,那么它回再次将该消息回交给View,View再回交给 Frame框架,框架检查自己是否对该消息有相应的处理函数,如果没有则递交给App,如果App也没有则由Windows系统自己处理。

    命 令消息:

    在以上三种消息中,标准的WINDOWS消息映射是相当简单的。可直接通过类向导完成不同消息的映射处理,所以不 在本文讨论之列。

    凡是从CcmdTarget类派生的类都可以有消息映射,消息映射包括如下两方面的内容:

    1、在类的定义文件中 (.H)中加上一条宏调用(通常这条语句中类定义的最后):

    DECLARE_MESSAGE_MAP()

    2、在类的实现文件(.CPP)中加上消息映射表:

    1

    BEGIN_MESSAGE_MAP(类名,父类名)

    2

    ………..

    3

    消息映射入口项.

    4

    ……….

    5

    END_MESSAGE_MAP()

    6

    幸运的是除了某些类(如没有基类的类或直接从CobjectO类派生的类)外,其它许多类均可由类向导生成.尽管生成的类只是一个框架,需要我 们补充内容。但消息映射表已经为我们加好了,只是入口项有待我们加入。命令消息映射入口项是一个ON_COMMAND的宏。比如文件菜单下的"打开…"菜 单(ID值为ID_FILE_OPEN)对应的消息映射入口项为:

    ON_COMMAND(ID_FILE_NEW,OnFileOpen)

    3、加入消息映射入口项之后需要完成消息处理函数。在类中消息处理函数都是类的成员函数,要响应一个消息,就必须定义一个该消息的处理函数。定义一个消 息处理函数包括以下三方面的内容:

    3.1 在类定义中加入消息处理函数的函数原型(函数声明)

    3.2 在类的消息映射表中加入相应的消息映射入口项

    3.3 在类的实现中加入消息处理函数的函数体

    需要说明的是消息处理函数的 原型一定要以afx_msg打头,作为约定,消息处理函数名一般以On打头。比如:

    afx_msg OnFileOpen();// 函数原型

    控件通知消息:

    控件通知消息相对而言就复杂 一点了,限于篇幅不能一一涉及,这里我们仅讨论 WM_NOTIFY消息的处理。

    WM_NOTFY产生的原因如下:

    在 WINDOWS3.X中控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗口。简单的通知仅发送一个WM_COMMAND消息, 包含一个通知码(比如BN_CLICKED)和一个在wParam中的控件ID及一个在lPraram中的控件句柄。因为wParam 和lParam均被使用,就没有方法传送其它的附加信息了。比如在BN_CLICKED 通知消息中,就没有办法发送关于当鼠标点击时光标的位置信息,在这种情况下就只能使用一些特殊的消息,包括:WM_CTLCOLOR、 WM_VSCROLL和 WM_HSCROLL等等。值得一提的是这些消息能被反射回发送它们的控件,就是所谓的消息反射。

    在WIN32中同 样可以使用那些在WINDOWS3.1中使用的通知消息,不过不像过去通过增加特殊目的的消息来为新的通知发送附加的数据,而是使用一个叫 WM_NOTIFY的消息,它能以一个标准的风格传送大量的附加数据。

    WM_NOTIFY消息包含一个存在wParam中的发送消息 控件的ID和一个存在lParam中的指向一个结构体的指针。这个结构可能是NMHDR结构体,也可能是第一个成员是NMHDR的更大的结构,因为 NMHDR是第一个成员,所以指向这个结构的指针也可以指向NMHDR。在许多情况下,这个指针将指向一个更大的结构,当你使用时必需转换它,只有很少的 通知消息,比如通用通知消息(它的名字以NM_打头)。工具提示控件的 TTN_SHOW和TTN_POP实际上在使用NMHDR结构.

    NMHDR结构包含了发送消息控件的句柄,ID及通知码(如TTN_SHOW),其格式如下:

    1

    TypedefsturcttagNMHDR{

    2

    HWNDhwndFrom;

    3

    UINTidFrom;

    4

    UINTcode;

    5

    }NMHDR;

    6

    对TTN_SHOW消息而言,code成员的值将设为TTN_SHOW。

    类向导可以 创建ON_NOTIFY消息映射入口并为你提供一个处理函数的框架,来处理 WM_NOTIFY类型的消息。ON_NOTIFY消息映射宏有如下语法:

    ON_NOTIFY(wNotifyCode,id,memberFxn)

    wNotifyCode:要处理的通知消息通知码,比如:LVN_KEYDOWN;Id:控件标识ID;MemberFxn:处理此消息的成员函数。此 成员函数必需有如下的原形申明:

    afx_msgvoidmemberFxn(NMHDR*pNotifyStruct,LRESULT*result);

    比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用类向导添加如下的消息映射:

    ON_NOTIFY(LVN_KEYDOWN,IDC_LIST1,OnKeydownList1)

    在上面的例子中,类向导提供如下函数:

    1

    voidCMessageReflectionDlg::OnKeydownList1(NMHDR*pNMHDR,LRESULT*pResult)

    2

    {

    3

    LV_KEYDOWN*pLVKeyDow=(LV_KEYDOWN*)pNMHDR;

    4

    //TODO:Addyourcontrolnotificationhandler

    5

    //codehere

    6

    *pResult=0;

    7

    }

    这时类向导提供了一个适当类型的指针.你既可以通过 pNMHDR,也可以通过 pLVKeyDow来访问这个通知结构。

    如前所述,有时我们可能需要为一组控件处理相同的WM_NOTIFY消 息,这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY。当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:

    ON_NOTIFY_RANGE(wNotifyCode,id,idLast,memberFxn)

    wNotifyCode: 消息通知码,比如:LVN_KEYDOWNid: 第一控件的标识IDidLast:最后一个控件的标识ID(标识值一定要连续);memberFxn: 消息处理函数。成员函数必须有如下原型申明:

    afx_msgvoidmemberFxn(UINTid,NMHDR*pNotifyStruct,LRESULT*result);

    其中id表示发送通知消息的控件标识ID。

    ON_NOTIFY

    这个技术文章介绍了关于新WM_NOTIFY消息, 还描述了建议使用的一种在你的MFC应用程序中处理WM_NOTIFY消息的方法。

    Windows 3.x 的 通告消息

    在Windows 3.x下,控件通过发送一个消息给它的父窗口来告知诸如目标点击,内容的变化与选中,控件北京绘制等等之类的事件。简单的通告消息以特殊的WM_COMMAND消息形式来发送,通知码(如BN_CLICKED)与控件ID存放在wParam里,lParam保存控件的句柄。此时注意,wParam与lParam已经装满了数据,再也传递不了别的数据了,这些消息只能是简单的通告消息。举个例子,BN_CLICKED通告消息,无法发送按下鼠标按键时鼠标的位置信息。

    当Windows 3.x下的控件需要发送包括额外数据的通告消息时,它们使用各种特殊目的的消息,包括WM_CTLCOLOR, WM_VSCROLL, WM_HSCROLL, WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM, WM_CHARTOITEM, WM_VKEYTOITEM等等。这些消息能够被反射回给发送它们的的控件,要看更多信息,查阅 TN062: Message Reflection for Windows Controls. (TN062: Windows控件的消息反射)

    Win32下的通告消息

    对于那些Windows 3.1的控件, Win32 API使用那些曾在Windows3.x有的绝大部分通告消息。However, Win32 also adds a number of sophisticated, complex controls to those supported in Windows 3.x.这些控件经常发送带附加数据的通告消息。设计者们没有为每一个需要附加数据的通告消息增加一个新的WM_* 消息,而是只增加了一个消息,WM_NOTIFY,这个消息可以通过一标准化格式传递任意多的额外数据。

    WM_NOTIFY消息包括 保存发送消息控件ID的wParam和保存一个结构指针的lParam两部分。那个结构可以是一个NMHDR结构或者某些更大点的、以NMHDR结构为第一个成员的结构。这样的话,一个指向该结构的指针可以是NMHDR结构指针,也可以是那个更大点的结构指针,看你怎么转换他了。

    在大多数情况下,那个指针会指向更大点的结构,当你用到的时候就需要转换它。 只有几个通告消息,如 common通告消息(名字以NM_开始),工具提示控件的TTN_SHOWTTN_POP,是实际上用到NMHDR结构的。

    那个NMHDR结构或者为首成员的结构,包含发送消息的控件句柄和ID,还有通知码(如TTN_SHOW)。NMHDR结构格式如下:

    typedef struct tagNMHDR {

    HWND hwndFrom;

    UINT idFrom;

    UINT code;

    } NMHDR;

    对于TTN_SHOW消息,成员code应被设置成TTN_SHOW

    大多数通告消息传递一个指向更大的把NMHDR结构作为第一个成员的结构的指针。举个例子,看看list view控件的LVN_KEYDOWN通告消息所使用的结构,在list view控件里键盘按键被按下时发送这个消息。那个指针就指向LV_KEYDOWN结构体,定义如下:

    typedef struct tagLV_KEYDOWN {

    NMHDR hdr;

    WORD wVKey;

    UINT flags;

    } LV_KEYDOWN;

    这样,因为NMHDR是这个结构的第一个成员,那个指针既可以转换为NMHDR型指针也可以转换为LV_KEYDOWN型指针。

    (笔者注:标准命名约定,LV_KEYDOWN已经改名为:NMLVKEYDOWN。MSDN Library - October 2001上是这么说的呵呵。)

    对新Windows控件通用的通告消息

    一些通告消息对所有新的Windows控件来说是通用的,这些通告消息传递一个指向NMHDR结构体的指针。

    Notification code

    Sent because

    NM_CLICK

    用户在控件内单击鼠标左键

    NM_DBLCLK

    用户在控件内双击鼠标左键

    NM_RCLICK

    用户在控件内单击鼠标右键

    NM_RDBLCLK

    用户在控件内双击鼠标右键

    NM_RETURN

    用户在控件具有输入焦点的时候按下回车

    NM_SETFOCUS

    控件获得输入焦点

    NM_KILLFOCUS

    控件失去输入焦点

    NM_OUTOFMEMORY

    控件因为没有足够的可用内存而不能完成一项操作

    ON_NOTIFY: 在MFC应用程序里处理 WM_NOTIFY 消息

    函数CWnd::OnNotify处理通告消息。其默认实现是检查通告消息处理函数的消息映射,然后调用。(checks the message map for notification handlers to call.)一般说来,你不用重载OnNotify。你可以写一个处理函数,然后在你自己的窗口类的消息映射表里添加一个该函数的消息映射入口。

    ClassWizard,通过ClassWizard属性页或者WizardBar工具条,能够创建ON_NOTIFY消息映射入口,并且给您提供了一个处理函数的框架。更多关于通过ClassWizard使得添加消息映射更容易的信息,请看Visual C++ Programmer's Guide的 Mapping Messages to Functions 。

    ON_NOTIFY 消息映射宏的语法如下:

    ON_NOTIFY( wNotifyCode, id, memberFxn )

    斜体字的参数被替换为:

    wNotifyCode

    要被处理的通告消息代码,如 LVN_KEYDOWN

    id

    发送通告消息的控件ID

    memberFxn

    通告消息发送后被调用的成员函数。

    你的成员函数必须按照如下形式声明:

    afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );

    斜体字参数为::

    pNotifyStruct

    指向通告消息结构的指针,类型如上声明。

    result

    指向函数返回之前要被设置结果值的变量指针。

    代码实例

    现指定你要成员函数OnKeydownList1函去处理ID为IDC_LIST1的CListCtrl控件的LVN_KEYDOWN消息,你可以通过ClassWizard把下面的内容加入到你的消息映射表里:

    ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )

    在上面的例子里,ClassWizard提供的函数是:

    void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)

    {

    LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;

    // TODO: Add your control notification handler

    // code here

    *pResult = 0;

    }

    ClassWizard自动生成合适类型的指针。你可以通过pNMHDR或者pLVKeyDow访问通告消息结构体。

    ON_NOTIFY_RANGE

    如果你需要处理一组控件的同一个WM_NOTIFY消息,你可使用ON_NOTIFY_RANGE而不是ON_NOTIFY。例如,你有一组按钮,想让它们对某一通告消息执行相同的动作。

    When you use ON_NOTIFY_RANGE, you specify a contiguous range of child identifiers for which to handle the notification message by specifying the beginning and ending child identifiers of the range.

    (不太会翻译,大意就是,使用ON_NOTIFY_RANGE的时候,要指定一个你所需要相同相同消息处理函数控件的ID范围)

    ClassWizard不去处理ON_NOTIFY_RANGE的使用,要用它,就自己在消息映射表里编辑。

    ON_NOTIFY_RANGE的消息映射入口与函数原型如下表示:

    ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )

    斜体字参数被替换为:

    wNotifyCode

    要被处理的通告消息代码,如 LVN_KEYDOWN

    id

    连续ID范围里的第一个。

    idLast

    连续ID范围里的最后一个。

    memberFxn

    通告消息发送后被调用的成员函数。

    你的成员函数必须按照如下形式声明:

    afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );

    斜体字参数为:

    id

    发送通告消息的控件ID

    pNotifyStruct

    指向通告消息结构的指针,类型如上声明。

    result

    指向函数返回之前要被设置结果值的变量指针。

    ON_NOTIFY_EX, ON_NOTIFY_EX_RANGE(这部分不理解,先放一放)

    If you want more than one object in the notification routing to handle a message, you can use ON_NOTIFY_EX (or ON_NOTIFY_EX_RANGE) rather than ON_NOTIFY (or ON_NOTIFY_RANGE). The only difference between the EX version and the regular version is that the member function called for the EX version returns a BOOL that indicates whether or not message processing should continue. Returning FALSE from this function allows you to process the same message in more than one object.

    ClassWizard does not handle ON_NOTIFY_EX or ON_NOTIFY_EX_RANGE; if you want to use either of them, you need to edit your message map yourself.

    The message-map entry and function prototype for ON_NOTIFY_EX and ON_NOTIFY_EX_RANGE are as follows. The meanings of the parameters are the same as for the non-EX versions.

    ON_NOTIFY_EX( nCode, id, memberFxn )

    ON_NOTIFY_EX_RANGE( wNotifyCode, id, idLast, memberFxn )

    The prototype for both of the above is the same:

    afx_msg BOOL memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );

    In both cases, id holds the child identifier of the control that sent the notification.

    Your function must return TRUE if the notification message has been completely handled or FALSE if other objects in the command routing should have a chance to handle the message.

    源文档 <http://www.cnblogs.com/lantionzy/archive/2009/10/10/1580428.html>

你可能感兴趣的:(mfc)