[转]windows消息机制(MFC)
消息分类与消息队列
Windows中,消息使用统一的结构体(MSG)来存放信息,其中message表明消息的具体的类型,
而wParam,lParam是其最灵活的两个变量,为不同的消息类型时,存放数据的含义也不一样。
time表示产生消息的时间,pt表示产生消息时鼠标的位置。
按照类型,Windows将消息分为:
(0) 消息ID范围
系统定义消息ID范围:[0x0000, 0x03ff]
用户自定义的消息ID范围:
WM_USER: 0x0400-0x7FFF (例:WM_USER+10)
WM_APP(winver> 4.0):0x8000-0xBFFF (例:WM_APP+4)
RegisterWindowMessage:0xC000-0xFFFF【用来和其他应用程序通信,为了ID的唯一性,使用::RegisterWindowMessage来得到该范围的消息ID 】
(1) 窗口消息:即与窗口的内部运作有关的消息,如创建窗口,绘制窗口,销毁窗口等。
可以是一般的窗口,也可以是MainFrame,Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等
(2) 当用户从菜单选中一个命令项目、按下一个快捷键或者点击工具栏上的一个按钮,都将发送WM_COMMAND命令消息。
LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID;如果是控件, HIWORD(wParam)表示控件消息类型。
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
(3) 随着控件的种类越来越多,越来越复杂(如列表控件、树控件等),仅仅将wParam,lParam将视为一个32位无符号整数,已经装不下太多信息了。
为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。
WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件ID,lParam为一个NMHDR指针,
不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大。
注:Window 9x 版及以后的新控件通告消息不再通过WM_COMMAND 传送,而是通过WM_NOTIFY 传送,
但是老控件的通告消息, 比如CBN_SELCHANGE 还是通过WM_COMMAND 消息发送。
(4) windwos也允许程序员定义自己的消息,使用SendMessage或PostMessage来发送消息。
windows消息还可以分为:
(1) 队列消息(Queued Messages)
消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理
如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠标,键盘消息等。
其中,WM_PAINT,WM_TIMER只有在队列中没有其他消息的时候才会被处理,
WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。
(2) 非队列消息(NonQueued Messages)
消息会绕过系统消息队列和线程消息队列,直接发送到窗口过程进行处理
如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED
Windows系统的整个消息系统分为3个层级:
① Windows内核的系统消息队列
② App的UI线程消息队列
③ 处理消息的窗体对象
Windows内核维护着一个全局的系统消息队列;按照线程的不同,系统消息队列中的消息会分发到应用程序的UI线程的消息队列中;
应用程序的每一个UI线程都有自己的消息循环,会不停地从自己的消息队列取出消息,并发送给Windows窗体对象;
每一个窗体对象都使用窗体过程函数(WindowProc)来处理接收到的各种消息。
1 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2 {
3 PAINTSTRUCT ps;
4 HDC hdc;
5
6 switch (message)
7 {
8 case WM_COMMAND:
9 break;
10 case WM_PAINT:
11 hdc = BeginPaint(hWnd, &ps);
12 // TODO: 在此添加任意绘图代码...
13 EndPaint(hWnd, &ps);
14 break;
15 case WM_DESTROY:
16 PostQuitMessage(0);
17 break;
18 default:
19 return DefWindowProc(hWnd, message, wParam, lParam);
20 }
21 return 0;
22 }
需要的话,在WindowProc中,可以用::GetMessageTime获取当前消息产生的时间,
用::GetMessagePos获取当前消息产生时鼠标光标所在的位置。
(1) 各个窗口消息由各个窗体(或控件)自身的WindowProc(虚函数)接收并处理。
(2) WM_COMMAND命令消息统一由当前活动主窗口的WindowProc接收,经过绕行后,可被其他的CCmdTarget对象处理。
(3) WM_COMMAND控件通知统一由子窗口(控件)的父窗口的WindowProc接收并处理,也可以进行绕行被其他的CCmdTarget对象处理。
(例如:CFormView具备接受WM_COMMAND控件通知的条件,又具备把WM_COMMAND消息派发给关联文档对象处理的能力,
所以给CFormView的WM_COMMAND控件通知是可以让文档对象处理的。)
另外,WM_COMMAND控制通知会先调用ReflectLastMsg反射通知子窗口(控件),如果子窗口(控件)处理了该消息并返回TRUE,则消息会停止分发;
否则,会继续调用OnCmdMsg进行命令发送(如同WM_COMMAND命令消息一样)。
注:WM_COMMAND命令消息与WM_COMMAND控件通知的相似之处:
WM_COMMAND命令消息和WM_COMMAND控制通知都是由WindowProc给OnCommand处理,
OnCommand通过wParam和lParam参数区分是命令消息或通知消息,然后送给OnCmdMsg处理。
事实上,BN_CLICKED控件通知消息的处理和WM_COMMAND命令消息的处理完全一样。
因为该消息的通知代码是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。
(4)WM_NOTIFY消息只是对WM_COMMAND控件通知进行了扩展,与WM_COMMAND控件通知具有相同的特点。
SendMessage与PostMessage
PostMessage 把消息投递到消息队列后,立即返回;
SendMessage把消息直接送到窗口过程处理,处理完才返回。
GetMessage与PeekMessage
GetMessage 有消息且该消息不为WM_QUIT,返回TRUE。
有消息且该消息为WM_QUIT,返回FALSE。
没有消息时,挂起该UI线程,控制权交还给系统。
PeekMessage 有消息返回TRUE,如果没有消息返回FALSE。
是否从消息队列中删除此消息(PM_REMOVE),由函数参数来指定。
要想在没有消息时做一些工作,就必须使用PeekMessage来抓取消息,以便在没有消息时,能在OnIdle中执行空闲操作(如下):
1 while (TRUE)
2 {
3 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)
4 {
5 if (msg.message == WM_QUIT)
6 break;
7 TranslateMessage(&msg);
8 DispatchMessage(&msg);
9 }
10 else
11 {
12 OnIdle();
13 }
14 }
例如:MFC使用OnIdle函数来清理一些临时对象及未使用的动态链接库。
只有在OnIdle返回之后程序才能继续处理用户的输入,因此不应在OnIdle进行较长的任务。
MFC消息处理
在CWnd中,MFC使用OnWndMsg来分别处理各类消息:
如果是WM_COMMAND消息,交给OnCommand处理;然后返回。
如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。
如果是WM_ACTIVATE消息,先交给_AfxHandleActivate处理,再继续下面的处理。
如果是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理,然后返回。
如果是其他的窗口消息(包括WM_ACTIVATE消息),则
首先在消息缓冲池(一个hash表,用于加快消息处理函数的查找)进行消息匹配,
若匹配成功,则调用相应的消息处理函数;
若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否能处理当前消息。
如果消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;
否则,该消息没有被应用程序处理,OnWndMsg返回FALSE。
MFC消息映射
消息映射实际是MFC内建的一个消息分派机制。
把MFC中的宏进行展开(如下),可以得到消息映射表整个全貌。
注:GetMessageMap为虚函数。
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0}:对象消息映射表的结束标识
窗口消息只能由CWnd对象来处理,采用向基类直线上朔的方式,来查找对应的消息响应函数进行处理。
一旦找到消息响应函数(若有返回值且为TRUE),就停止上朔。因此,我们经常会看到这样的代码:
增加一个消息处理函数来写我们的逻辑时,MFC ClassWizard会在该函数之前或之后显示调用其基类对应的函数,保证基类中逻辑被执行。
命令消息可由CCmdTarget对象接收并处理(OnCmdMsg为虚函数),除了向基类直线上朔方式外,还有命令绕行机制(要防止形成圈,死循环)。
在某种程度上,控制通知消息由窗口对象处理是一种习惯和约定。然而,控件通知消息也是可以有CCmdTarget对象接收并处理,并进行命令绕行的。
下图为MFC经典单文档视图框架的命令消息绕行路线:
函数调用过程如下(如果没有任何对象处理该条WM_COMMAND消息,最后会被::DefWindowProc处理)。
非模态对话框的消息处理
1 static CAboutDlg aboutDlg;
2 aboutDlg.Create(IDD_ABOUTBOX, this);
3 aboutDlg.ShowWindow(SW_SHOW);
应用程序只有一个消息循环。
对于窗口消息,非模态对话框(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。
对于命令消息,由当前活动主窗口的WindowProc接收(例如:当前活动主窗口为非模态对话框,则命令消息会被非模态对话框接收)。
可以在当前活动主窗口的OnCmdMsg中做命令绕行,使得其他的CCmdTarget对象也可以处理命令消息。
对于控件通知,由其父窗口的WindowProc接收并处理,一般不进行命令绕行被其他的CCmdTarget对象处理。
模态对话框的消息处理
1 CAboutDlg aboutDlg;
2 aboutDlg.DoModal();
(1) 模态对话框弹出来后,首先会让父窗口失效,使其不能接受用户的输入(键盘鼠标消息)。
1 EnableWindow(hwndParent, FALSE) ;
(2) 父窗口消息循环被阻塞(会卡在DoModal处,等待返回),由模态对话框的消息循环来接管(因此整个程序不会卡住)。
接管后,模态对话框的消息循环仍然会将属于父窗口及其子控件的窗口消息(不包括键盘鼠标相关的窗口消息)发送给它们各自的WindowProc窗口函数,进行响应处理。
(3) 模态对话框销毁时(点击IDOK或IDCANCEL),父窗口消息循环重新激活,继续DoModal后的逻辑。
激活后,父窗口有可以重新接受用户的输入(键盘鼠标消息)。
1 EnableWindow(hwndParent, TRUE) ;
从上面的过程中,我们可以得到如下结论:
对于窗口消息,模态对话框主窗口(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。
只是父窗口(及其子控件)无法接受到键盘鼠标消息相关的窗口消息。
对于命令消息,由模态对话框主窗口的WindowProc接收。可以在模态对话框主窗口的OnCmdMsg中做命令绕行,使得其他的CCmdTarget对象也可以处理命令消息。
对于控件通知,由其父窗口的WindowProc接收并处理,一般不进行命令绕行被其他的CCmdTarget对象处理。
参考
《深入浅出MFC》- 侯捷
转载:http://www.cnblogs.com/kekec/p/3210696.html
[转]windows消息机制(MFC)的更多相关文章
- 我对windows消息机制的理解(参考深入浅出MFC,欢迎批评指正!!)
以消息为基础,以事件驱动之 程序的进行依靠外部消息来驱动,即:程序不断等待任何可能的输入,然后做判断,然后再做适当的处理. 消息输入:操作系统捕获,以消息形式进入程序.(操作系统通过其USERS模块中 ...
- 转:Windows消息机制要点
Windows消息机制要点 1. 窗口过程 每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ...
- windows消息机制(转)
1. 引言Windows 在操作系统平台占有绝对统治地位,基于Windows 的编程和开发越来越广泛.Dos 是过程驱动的,而Windows 是事件驱动的[6],这种差别的存在使得很多Dos 程序员不 ...
- 深入理解windows 消息机制
深入理解Windows消息机制 今天我们来学一学Windows消息机制,我们知道在传统的C语音程序中,当我们需要打开一个文件时,我们可以调用fopen()函数,这个函数最后又会调用操作系统提供的函数以 ...
- Windows消息机制
Windows的消息系统是由3个部分组成的: · 消息队列.Windows能够为所有的应用程序维护一个消息队列.应用程序必须从消息队列中获取消息,然后分派给某个窗口.· 消息循环.通过这个循环机制应用 ...
- <Win32_1>深入浅出windows消息机制[转自crocodile_]
上学期学习了Java ,感觉Java写一个窗口真心简单,很易上手,也就难怪很多开发人员选择Java作为自己的开发编程语言.但是由于自身对windows的热爱,让我觉得c.c++语言才是我亲睐的编程语言 ...
- 深入Delphi -- Windows 消息机制
http://www.txsz.net/xs/delphi/3/Windows%20%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6.htm Windows 消息机制 by m ...
- 收藏:Windows消息机制
百度百科介绍的windows消息机制也不错:http://baike.baidu.com/view/672379.htm Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视 ...
- windows消息机制框架原理【简单版本】
windows消息机制框架原理 结合两张图理解 窗口和窗口类 Windows UI 应用程序 (e) 具有一个主线程 (g).一个或多个窗口 (a) 和一个或多个子线程 (k) [工作线程或 UI 线 ...
- windows消息机制(MFC)
消息分类与消息队列 Windows中,消息使用统一的结构体(MSG)来存放信息,其中message表明消息的具体的类型, 而wParam,lParam是其最灵活的两个变量,为不同的消息类型时,存放数据 ...
随机推荐
- 阿里云配置redis
一.redis redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sor ...
- angularJS前端分页插件
首先在项目中引入 分页插件的 js 和 css: 在html页面引入 相关js 和 css: 在控制器中引入分页插件中定义的 module[可以打开pagination.js查看,可以看到 其实,在插 ...
- STL之六:map/multimap用法详解
转载于:http://blog.csdn.net/longshengguoji/article/details/8547007 map/multimap 使用map/multimap之前要加入头文件# ...
- jq 数组定义,拼接~~~push应用
var radio_checked_array = []; $("input[type='radio']").each(function(){ if($(this).attr('c ...
- c# string 转 GUID
提供两种方法 1.try...catch... /* * string TO guid */ private static bool ToGuid(string str) { Guid gv = ne ...
- Mybatis(1) 创建Mybatis HelloWorld
简介: MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 ...
- [洛谷P3460] [POI2007]TET-Tetris Attack
洛谷题目链接:[POI2007]TET-Tetris Attack 题目描述 A puzzle called "Tetris Attack" has lately become a ...
- 【洛谷 P1073】 最优贸易 (Tarjan缩点+拓扑排序)
题目链接 先\(Tarjan\)缩点,记录每个环内的最大值和最小值. 然后跑拓扑排序,\(Min[u]\)表示到\(u\)的最小值,\(ans[u]\)表示到\(u\)的答案,\(Min\)和\(an ...
- 【洛谷 P2783】 有机化学之神偶尔会做作弊 (双联通分量)
题目链接 可能是除了<概率论>的最水的黑题了吧 用\(Tarjan\)缩点(点双联通分量),然后就是树上两点之间的距离了,跑\(LCA\)就好了. #include <cstdio& ...
- Transformation(线段树+HDU4578+多种操作+鬼畜的代码)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4578 题目: 题意:n个数初始值为0,进行四种操作:1.将区间内的数字加c:2.将区间内的数字乘c:3 ...