mfc把消息分为3大类
1.命令消息(wm_command)
一般来自工具栏和菜单栏,凡是派生自CCmdTarget的类,都可以接收命令消息。
2,标准消息(wm_)
凡是派生自cwnd类,都可以接收该消息。
3.notify
由控件产生,向其父窗口通知某种情况。
Windows 消息
消息可以分为系统定义消息和应用定义消息两大类。
- 系统保留的消息标识符值的范围是 0x0000 到 0x03FF(WM_USER - 1)。应用不能使用这些值作为私有消息
- 在 0x0400(WM_USER)到 0x7FFFF 范围内的值用于私有窗口类的消息标识符。
windows使用两种方法将消派发到一个窗口消息处理函数:一是将消息放到消息队列(先进先出队列),二是不放到消息队列,直接发送到窗口消息处理函数,让窗口处理函数来处理消息。
派发到消息队列的消息被称为排队消息(Queued messages)。它们主要是用户输入事件,比如说鼠标或键盘消息盘,有WM_MOUSEMOVE消息,WM_LBUTTONDOWN,WM_KEYDOWN,和WM_CHAR消息。还有一些其他的,包括WM_TIMER,WM_PAINT,以及WM_QUIT。大多数其他的消息息,这是直接发送到窗口过程,被称为非队列消息(non queued messages)。
(1) 队列(Queued)消息
windows可同时显示任意数量的窗口。此时,系统使用消息队列来将键盘和鼠标事件正确的派发到正确的窗口。
windows维护着一个系统消息队列,以及分别为每个GUI线程维护一个各自的线程消息队列。为了避免非GUI线程的创建线程消息队列的开销,所有线程创建初始化时,均不创建消息队列。只有当线程第一次调用GDI函数时,系统才会为线程创建消息队列。所以那些非GUI线程是没有消息队列的。
每当用户移动鼠标,点击按钮或键盘时,鼠标或键盘的设备驱动程序会将输入转换成消息,并将消息放在系统消息队列里。删windows会检查自己的消息队列,如果消息队列不为空,则每次取出并删除一个消息,然后确定消息的目标窗口,然后把消息放到创建这个窗口的线程的线程消息队列里。线程的消息队列接收由线程创建的窗口的所有的鼠标和键盘消息。然后线程会从队列中删除信息,并告诉系统把它们派发到对应的窗口消息处理函数。
除了WM_PAINT, WM_TIMER和WM_QUIT消息以外,系统总是派发放在在消息队列的末尾的消息。这将保证让一个窗口以first-in, first-out的顺序接收消息。WM_PAINT,WM_TIMER,和WM_QUIT消息,会一直被保存在队列中,只有在队列中没有其他消息时才会被派发到窗口消息处理函数。此外,同一个窗口的多个WM_PAINT消息被合并成一个WM_PAINT消息,客户区的所有无效部分也会被合并。这样是为了减少窗口重绘客户区的次数。
系统通过填充一个 MSG 结构来将消息投递到线程的消息队列,随后将其拷贝到消息队列中。 MSG 结构的信息包括:指定窗口的句柄,消息标识符,两个消息参数,消息投递的时间,以及鼠标光标的位置。通过使用 PostMessage (异步的)和 PostThreadMessage 函数,线程可以将一个消息投递到自己的消息队列或其他线程的消息队列。
应用可以使用 GetMessage 来删除队列中的消息。要在不删除消息的情况下检查队列消息,应用可以使用 PeekMessage 函数,该函数会使用消息填充 MSG 。
在从队列删除消息后,应用可以使用 DispatchMessage 函数来指示系统把消息发送给窗口过程进行处理。DispatchMessage 接收一个 MSG 结构的指针,该结构已经使用 GetMessage 或 PeekMessage 填充过。DispatchMessage 将窗口句柄,消息标识符,和两个消息参数传递给窗口过程,但它不会传递时间和鼠标光标位置。应用在处理消息时可以通过 GetMessageTime 和 GetMessagePos 函数检索时间和位置信息。
(2) 非队列(Nonqueued)消息
Nonqueued消息被立即送往目的地的窗口消息处理函数,绕过了系统的消息队列和线程消息队列。系统通常会发送nonqueued消息,来通知那些会影响窗口的事件。例如,当用户激活一个新的应用程序窗口时,系统会发送一些列消息到窗口,包括WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSOR。这些消息通知窗口被激活,键盘输入被定向到窗口,并且鼠标光标也移到窗口的边界内。
Nonqueued消息也有可能来源于应用程序调用系统函数。例如,系统调用SetWindowPos函数移动一个窗口后会发送WM_WINDOWPOSCHANGED消息。 一些函数也发送nonqueued消息, 有BroadcastSystemMessage,BroadcastSystemMessageEx,SendMessage(同步),SendMessageTimeout,和SendNotifyMessage。
常见的消息
WM_CREATE
当应用通过调用 CreateWindow 或 CreateWindow 要求创建一个窗口时,会发送这个消息(在函数返回前消息就被发送)。新窗口的窗口过程在窗口创建后会接收到这个消息,但是是在窗口可见之前。
这是窗口过程接收到的第一个消息。
接收该消息时,窗口过程的 wParam 参数值不被使用,它的 lParam 是一个指向 CREATESTRUCT 结构的指针。这个结构包含了窗口初始化的参数。
处理该消息后,窗口过程应该返回 0 以继续窗口的创建。如果窗口过程返回 -1,窗口会被销毁,CreateWindow 或 CreateWindowEx 会返回空句柄。
WM_SIZE
在窗口尺寸改变后向窗口发送该消息。
wParam 是尺寸改变的类型,它可以是下列值中的一个;
- SIZE_MAXHIDE,当其他窗口最大化时,该消息会发送到所有弹出(pop-up)窗口
- SIZE_MAXIMIZED,窗口已经最大化了
- SIZE_MAXSHOW,当其他窗口恢复到之前尺寸时,该消息会发送到所有弹出窗口
- SIZE_MINIMIZED,窗口已经最小化了
- SIZE_RESTORED,窗口的尺寸改变了,但不是最大化和最小化
lParam 的低位是客户区的新宽度,高位是客户区的新高度。虽然窗口的宽度和高度是 32 位值,lParam 只包含宽高值的低 16 位。
窗口过程处理该消息后应该返回 0。
WM_PAINT
当系统或其他应用要求对应用窗口的部分进行绘制时,会发送该消息。调用 UpdateWindow 或 RedrawWindow 函数时,或在应用通过使用 GetMessage 或 PeekMessage 获得 WM_PAINT 并调用 DispatchMessage 函数后,消息会被发送到窗口过程。
lParam 和 wParam 都不被使用。
Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区。效果很明显,调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。如果你调用Invalidate之后想马上更新屏幕,那就加上UpdateWindow()这条语句。
WM_DESTROY
当窗口将被销毁时发送该消息。在窗口被从屏幕删除后,它会被发送到删除的窗口的窗口过程。
该消息首先被发送到被销毁的窗口,之后发送到子窗口(如果有的话)。在消息的主窗口处理过程中,可以假设所有子窗口还是存在的。
wParam 和 lParam 不被使用。如果处理了该消息,窗口过程应返回 0。
如果被销毁的窗口是剪切板链的一部分,在 WM_DESTROY 消息处理返回前,窗口必须将它从链条中移除。
WM_COMMAND
当用户从菜单选中一个命令时会发送,当控件向它的父窗口发送提醒消息时会发送,当加速键被翻译时会发送。
如果应用处理了该消息,它应该返回 0。
在消息来源是菜单时,wParam 的高位是 0,低位是菜单标识符(IDM_*),lParam 是 0。
消息来源是加速键时,wParam 的高位是 1,低位是加速键标识符(IDM_*),lParam 是 0。
消息来源是控件时,wParam 的高位是控件特定的通知码,低位是控件标识符,lParam 是控件窗口句柄。
命令
ON_COMMAND用来响应相应工具栏和菜单栏的命令WM_COMMAND,不用自己解开WM_COMMAND中wParam和lParam中传送的控件ID。ON_COMMAND对应的消息ID一直都是WM_COMMAND.
操作方法:在类向导中,命令tab页 对象id为选择需要的控件ID,消息为command.然后添加处理程序。相当于在
在头文件添加了 afx_msg void func();
在源文件的BEGIN_MESSAGE_MAP添加了ON_COMMAND(控件ID,func);
消息
如果是系统已定义好的消息,其格式为ON_WM_XX的形式。
操作办法是:在类向导中,消息tab页选择某一个WM_XXX即可。
相当于在在头文件添加了 afx_msg void func();
在源文件的BEGIN_MESSAGE_MAP添加了ON_WM_XX。
ON_MESSAGE用来响应自定义消息,能够处理所有的消息响应,在程序中需要自己设定相应的消息响应函数。
ON_MESSAGE(message, memberFxn ) 参数:
message:消息的ID。
memberFxn :映射message的消息函数,该函数的类型必须是以下类型的
afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM)。
操作办法
1.在头文件定义 #define WM_MYMESSAGE (WM_USER + 100) //消息id
2.在头文件定义afx_msg LRESULT func( WPARAM wparam, LPARAM lparam)。
3.在BEGIN_MESSAGE_MAP 添加ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
4.在源文件添加实现
LRESULT 类名::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
return 0;
}
需要调用postMessage 或者 SendMessage 实现关联。
ON_NOTIFY是控件向其父窗口发送消息处理的宏,扩展了ON_COMMAND的功能,使用了相应的NMHDR结构.
ON_NOTIFY( wNotifyCode, id, memberFxn )
wNotifyCode:要被处理的通告消息代码,如 LVN_KEYDOWN。
id:发送通告消息的控件ID。
memberFxn:通告消息发送后被调用的成员函数。
做法
1在头文件定义
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );
2.在BEGIN_MESSAGE_MAP 添加 ON_NOTIFY( wNotifyCode, id, memberFxn)
3.在源文件实现void 类名::memberFxn(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
}
虚函数OnCommand
virtual BOOL OnCommand(WPARAM wParam,LPARAM lParam);
相当于一个“接口”实现多个命令的处理。可以直接在类向导操作。
wParam:表示具体是哪个控件ID.
虚函数OnOntify
virtual BOOL OnNotify(WPARAM
wParam,
LPARAM
lParam,
LRESULT
* pResult)
NMHDR* pNMHDR = (NMHDR*)lParam;
NMHDR {
HWnd hWndFrom 相当于原WM_COMMAND传递方式的lParam
UINT idFrom 相当于原WM_COMMAND传递方式的wParam(low-order)
UINT code 相当于原WM_COMMAND传递方式的Notify Code(wParam"s high-order)
};
WindowProc //回调函数,处理发送给窗口的消息 手段更丰富了。 是一个虚函数,可以通过类向导添加。
LRESULT CALLBACK WindowProc( HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
hwnd:指向窗口的句柄。
uMsg:指定消息类型。uMsg可以是WM_COMMAND、 WM_NOTIFY 从而可以实现ON_NOTIFY ON_COMMAND的处理。
wParam:指定其余的、消息特定的信息。该参数的内容与UMsg参数值有关。
IParam:指定其余的、消息特定的信息。该参数的内容与uMsg参数值有关
CWnd::WindowProc 调用 OnWndMsg 用来分辨并处理消息;如果是命令消息,交给 OnCommand 处理,如果是通知消息(Notification),交给 OnNotify 处理。而一般的 Windows 消息,就直接在消息映射表中上溯,寻找其归宿(消息处理程序)
在CWnd中,MFC使用OnWndMsg来分别处理各类消息:
如果是WM_COMMAND消息,交给OnCommand处理;然后返回。
如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。
其中:OnCommand、OnNotify也是虚函数
一般 windows 消息:直线上溯,即从派生类流向到基类。
WM_COMMAND命令消息:拐弯上溯
DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP宏
具体方式是在类或者结构体末尾添加DECLARE_MESSAGE_MAP(无分号),然后在定义类成员函数的.CPP文件中,使用BEGIN_MESSAGE_MAP()宏和 END_MESSAGE_MAP()宏来实现对消息的处理。 其中BEGIN_MESSAGE_MAP(参数1,参数2),参数1为该类的类名,参数2为该类基类的类名。
DECLARE_MESSAGE_MAP
#define DECLARE_MESSAGE_MAP() \
protected: \
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
BEGIN_MESSAGE_MAP
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
static const AFX_MSGMAP_ENTRY _messageEntries[] = \
{
END_MESSAGE_MAP
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
static const AFX_MSGMAP messageMap = \
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
}
可以通过类的成员函数GetMessageMap获取到消息映射表的信息。
在MFC中消息映射表是从CCmdTarget开始继承下去的。
会发现这和 RTTI 一样,还是一个链表。将子类的消息映射表和父类的消息映射表联系起来。
下图为 MFC 消息映射表
消息传递
1.如果是一般的Windows消息(wm_xx),则一定是由派生类流向基类。没有旁流的可能。
2.如果是命令消息WM_COMMAND,就有奇特的路线。
3.cwinthread 没有declare、begin、end 宏组。所以BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget)是CCmdTarget而不是cwinthread,所以CWinApp可以跳过cwinthread直接连上CCmdTarget。
4.通过pbasemap 实现消息映射的继承性。即子类可以继承基类的消息。类似于虚函数,但不是虚函数的机制,可以减少额外的内存负担。
5. 在cwinapp::run 调用pumpmessage,而pumpmessage先调用GetMessage函数获得或者TranslateMessage函数,然后又调用DispatchMessage传递消息,从而把消息推送到afxwndproc,最后流向pwnd->windowproc。消息被分发到回调函数(过程函数),作用是消息传递给操作系统,然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息
在mfc2.5时代(九几年代),所有窗口类共享同一个窗口函数(即afxwndproc)。
但现在使用的是钩子技术(即hook),所以要关联hook章节一起看。
hook操作是在每一个cwnd派生类之对象产生之际发生。表示每一个从cwnd派生的类都有一个钩子函数。如frame、view。
即:每个窗口类有一个窗口消息处理函数。
所以DispatchMessage把消息推送到hook技术中的afxwndproc.然后afxwndproc -> AfxCallWndProc -> WindowProc(该函数是虚函数,可重写实现自己的内容) ->OnWndMsg(用来分辨并处理消息,
如果是命令消息,就调用OnCommand(该函数也是虚函数,好多派生类都可以重写,如cwnd、cframe);
如果是通知消息,就调用OnNotify。
而一般Windows消息,直接在消息映射表中上溯,即在OnWndMsg中可以实现)
下面图是:一般 windows 消息:直线上溯。
说明了基类的函数先执行,派生类的重写函数、或者重载函数后执行。
能够上溯,使用的技术是消息映射机制,因为在消息映射中就和基类(pbasemap)进行关联
下面图是:WM_COMMAND命令消息
在cframewnd::oncmdmsg处分3路处理。
说明:当framewnd收到wm_command时,消息传递的路线是 cview -> cdoc -> cframewnd -> cwinapp 这样的先后顺序。
延伸:
除了message map还有 data 、dispatchmap、event map.