windows消息机制(MFC)

windows消息机制(MFC

windows消息机制(MFC


来源:

http://www.cnblogs.com/findumars/p/3948427.html

博客园   ——很有价值的一个博客


朝闻道

朝闻道,夕可死矣!为了成为IT高手,为了挽回我失去的青春,也为了我亲爱的家人,下决心刻苦学习编程知识,虽九死而不悔!金头盔飞行员蒋佳冀:知道了不行,熟悉也不够,要真正进入潜意识,成为条件反射才行。



消息分类与消息队列

Windows中,消息使用统一的结构体(MSG)来存放信息,其中message表明消息的具体的类型,

wParamlParam是其最灵活的两个变量,为不同的消息类型时,存放数据的含义也不一样。

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) 随着控件的种类越来越多,越来越复杂(如列表控件、树控件等),仅仅将wParamlParam将视为一个32位无符号整数,已经装不下太多信息了。

    为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。

    WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件IDlParam为一个NMHDR指针,

    不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大。

注:Window 9x版及以后的新控件通告消息不再通过WM_COMMAND传送,而是通过WM_NOTIFY传送,
     
但是老控件的通告消息,比如CBN_SELCHANGE还是通过WM_COMMAND消息发送。

(4) windwos也允许程序员定义自己的消息,使用SendMessagePostMessage来发送消息。

windows消息还可以分为:

(1) 队列消息(QueuedMessages) 
消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理 
如:WM_PAINTWM_TIMERWM_CREATEWM_QUIT,以及鼠标,键盘消息等。
其中,WM_PAINTWM_TIMER只有在队列中没有其他消息的时候才会被处理,
WM_PAINT
消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。

(2) 非队列消息(NonQueuedMessages) 
消息会绕过系统消息队列和线程消息队列,直接发送到窗口过程进行处理 
如:WM_ACTIVATE, WM_SETFOCUS,WM_SETCURSORWM_WINDOWPOSCHANGED

Windows系统的整个消息系统分为3个层级:

    Windows内核的系统消息队列

    AppUI线程消息队列

    处理消息的窗体对象

Windows内核维护着一个全局的系统消息队列;按照线程的不同,系统消息队列中的消息会分发到应用程序的UI线程的消息队列中;

应用程序的每一个UI线程都有自己的消息循环,会不停地从自己的消息队列取出消息,并发送给Windows窗体对象;

每一个窗体对象都使用窗体过程函数(WindowProc来处理接收到的各种消息。

 1 LRESULT CALLBACK WindowProc(HWND hWnd, UINTmessage, 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消息派发给关联文档对象处理的能力,

         所以给CFormViewWM_COMMAND控件通知是可以让文档对象处理的。)

     另外,WM_COMMAND控制通知会先调用ReflectLastMsg反射通知子窗口(控件),如果子窗口(控件)处理了该消息并返回TRUE,则消息会停止分发;

     否则,会继续调用OnCmdMsg进行命令发送(如同WM_COMMAND命令消息一样)。

注:WM_COMMAND命令消息与WM_COMMAND控件通知的相似之处:
WM_COMMAND
命令消息和WM_COMMAND控制通知都是由WindowProcOnCommand处理,
OnCommand
通过wParamlParam参数区分是命令消息或通知消息,然后送给OnCmdMsg处理。
事实上,BN_CLICKED控件通知消息的处理和WM_COMMAND命令消息的处理完全一样。
因为该消息的通知代码是0ON_BN_CLICKED(idmemberfunction)ON_COMMAND(idmemberfunction)是等同的。

4WM_NOTIFY消息只是对WM_COMMAND控件通知进行了扩展,与WM_COMMAND控件通知具有相同的特点。

SendMessagePostMessage

PostMessage 把消息投递到消息队列后,立即返回; 
SendMessage
把消息直接送到窗口过程处理,处理完才返回。

GetMessagePeekMessage

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),就停止上朔。因此,我们经常会看到这样的代码:

增加一个消息处理函数来写我们的逻辑时,MFCClassWizard会在该函数之前或之后显示调用其基类对应的函数,保证基类中逻辑被执行。

命令消息可由CCmdTarget对象接收并处理(OnCmdMsg为虚函数),除了向基类直线上朔方式外,还有命令绕行机制(要防止形成圈,死循环)。

在某种程度上,控制通知消息由窗口对象处理是一种习惯和约定。然而,控件通知消息也是可以有CCmdTarget对象接收并处理,并进行命令绕行的。

下图为MFC经典单文档视图框架的命令消息绕行路线:

函数调用过程如下(如果没有任何对象处理该条WM_COMMAND消息,最后会被::DefWindowProc处理)。

非模态对话框的消息处理

1 static CAboutDlgaboutDlg;

2aboutDlg.Create(IDD_ABOUTBOX, this);

3aboutDlg.ShowWindow(SW_SHOW);

应用程序只有一个消息循环。

对于窗口消息,非模态对话框(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。

对于命令消息,由当前活动主窗口的WindowProc接收(例如:当前活动主窗口为非模态对话框,则命令消息会被非模态对话框接收)。

可以在当前活动主窗口的OnCmdMsg中做命令绕行,使得其他的CCmdTarget对象也可以处理命令消息。

对于控件通知,由其父窗口的WindowProc接收并处理,一般不进行命令绕行被其他的CCmdTarget对象处理。

模态对话框的消息处理

1 CAboutDlgaboutDlg;

2aboutDlg.DoModal();

(1) 模态对话框弹出来后,首先会让父窗口失效,使其不能接受用户的输入(键盘鼠标消息)。

1EnableWindow(hwndParent, FALSE) ;

(2) 父窗口消息循环被阻塞(会卡在DoModal处,等待返回),由模态对话框的消息循环来接管(因此整个程序不会卡住)。

    接管后,模态对话框的消息循环仍然会将属于父窗口及其子控件的窗口消息(不包括键盘鼠标相关的窗口消息)发送给它们各自的WindowProc窗口函数,进行响应处理。

(3) 模态对话框销毁时(点击IDOKIDCANCEL),父窗口消息循环重新激活,继续DoModal后的逻辑。

    激活后,父窗口有可以重新接受用户的输入(键盘鼠标消息)。

1EnableWindow(hwndParent, TRUE) ;

从上面的过程中,我们可以得到如下结论:

对于窗口消息,模态对话框主窗口(及其子控件)与父窗口(及其子控件)都是用自身的WindowProc函数接收并处理,互不干扰。

只是父窗口(及其子控件)无法接受到键盘鼠标消息相关的窗口消息。

对于命令消息,由模态对话框主窗口的WindowProc接收。可以在模态对话框主窗口的OnCmdMsg中做命令绕行,使得其他的CCmdTarget对象也可以处理命令消息。

对于控件通知,由其父窗口的WindowProc接收并处理,一般不进行命令绕行被其他的CCmdTarget对象处理。

参考

《深入浅出MFC-侯捷

MFC教程》消息映射的实现

http://blog.youkuaiyun.com/kongfuxionghao/article/details/35882533

分类:VC++ MFC,Delphi-消息大全

 

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一成为扩展节点的机会,且会一性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值