2020.10
一、MessageBox和定时器TIMER
MessageBox是Win32 API全局函数,必须指定标题和样式。共有4个参数。没有父窗口就NULL。返回值是int,看选什么按钮。
比如:MessageBox( NULL, "选什么?", "标题", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
::表示全局函数。
在MFC中,可使用全局API函数::MessageBox(NULL,………),子程序会被暂时中断在这个MessageBox上!其后的代码无法运行,等到MessageBox返回后会继续运行代码。但父窗口可以被点出来,其它程序段可以运行,比如其它按钮。 因为NULL没有指定父窗口,所以不会使父窗口无效!
MFC中直接使用MessageBox,则是使用CWnd类的MessageBox方法,这时父窗口无法被点出来,等同于::MessageBox(m_hWnd,………)。同时子程序被暂时中断在这个MessageBox上。但关了其中一个消息框,则父窗就可以点出来了。 原理应该是m_hWnd 指定了父窗,MessageBox使父窗口无效,然后进入自己的消息循环,关闭时,使父窗有效。
在TIMER的响应函数中,有MessageBox的话,则会暂时中断在这个MessageBox上,但当下个时间到时,又会出现一个新的MessageBox,且也是暂时中断在这个MessageBox上... 即出现了多个消息响应函数的例程!!多次重入!!每个例程都中断在MessageBox上,MessageBox返回后,例程会继续执行到结束! 各个消息框都是父窗的子窗,所以是平等的,所以各个消息框都可以点出来,但父窗点不出来。 可以关闭任何一个消息框,但是这个重入例程MessageBox之后的代码不会运行,要按倒序关闭其后的消息框后才会运行。
二、消息框原理分析:参考《深入探讨MFC消息循环和消息泵》和调试时断点看代码
注意:
MFC是单线程运行的。
MFC内部是有消息循环。
消息循环的DispatchMessage()会阻塞,需要等窗口消息处理函数处理完了以后,才会返回。
窗口消息处理函数是由操作系统调用的。
窗口消息处理函数是可以多次重入的!类似函数自己可以嵌套一样。
单线程运作:阻塞→重入→阻塞→重入。。。
时间到→操作系统放WM_TIMER到线程队列→MFC消息循环DispatchMessage()阻塞→系统调用窗口消息处理函数OnTimer()处理完→消息循环继续
操作系统一到时间就会放WM_TIMER到线程队列。因为是单线程,如果窗口消息处理函数如果一直没处理完,则WM_TIMER会堆积在队列中。
OnTimer()中有消息框时情况就复杂了。MessageBox内部是一个模态对话框,模态对话框有消息循环。
时间1到,系统放WM_TIMER到队列
↓
MFC程序主消息循环DispatchMessage()阻塞
↓
操作系统调用第1个OnTimer()
↓
MessageBox生成一个模态对话框对象1,显示出来,运行对话框内的消息循环1
↓
时间2到,系统放WM_TIMER到队列
↓
MessageBox1的消息循环1 DispatchMessage()阻塞
↓
操作系统调用第2个OnTimer() (函数重入了!)
↓
MessageBox生成一个模态对话框对象2,显示出来,运行对话框内的消息循环2
↓
时间3到,MessageBox2的消息循环2 DispatchMessage()阻塞,操作系统调用第3个OnTimer() (函数重入了!)。。。
即一个串一个,只有最后一个消息框的消息循环在起作用,前面的消息循环全部阻塞了。
OnTimer()是主窗(父窗)函数,所以各个MessageBox都是子窗,各个子窗是都可以点出来,但父窗会被子窗失效掉,所以父窗点不出来。
当任何一个子窗关掉后,会使父窗有效,这时父窗可以点出来了。
三、消息框对话框关闭时原理
关闭时是怎样?为什么关闭时是按倒序在执行代码?
MessageBox无法调试进入内部看代码,就用模态对话框来代替吧:
CAboutDlg dlgAbout;
dlgAbout.DoModal(); // DoModal就会生成模态对话框
CDialog::DoModal()里面有CreateRunDlgIndirect()。
CWnd::CreateRunDlgIndirect里面有RunModalLoop()。
CWnd::RunModalLoop里面有消息循环AfxPumpMessage(),这里一直进行消息循环!循环中还有CWnd::ContinueModal()判断m_nFlags标志看是否需要退出循环。
当关闭对话框时,如果点“确定”就是触发CAboutDlg::OnOK(),点“X”就是触发CAboutDlg::OnCancel()。 这两个函数进去都是运行EndDialog()。
CDialog::EndDialog里面先运行EndModalLoop(),然后是运行::EndDialog()。
CWnd::EndModalLoop()里面有
m_nFlags &= ~WF_CONTINUEMODAL; 把标志设置成不再继续运行对话框
PostMessage(WM_NULL); 发送一个空消息以确保消息队列中有消息,这样消息循环不会因为没有消息而阻塞
::EndDialog()无法进入看代码,功能是把对话框关了不显示,然后把父窗激活。
把对话框关了不显示,这样我们就点不到这个对话框,就不会再触发窗口消息处理函数了。
所以清楚了:
点击关闭其中一个消息框时(比如消息框1),生成了一个消息(这个消息是关联这个消息框1的),“最后一个”消息框(比如消息框3)的消息循环DispatchMessage()这个消息后,操作系统重入消息框1对象的OnOK()或OnCancel(),设置m_nFlags标志成不再继续运行,发送一个消息框1的消息WM_NULL,以及用::EndDialog()把消息框1销毁了并把父窗激活。
这个时候,这个消息框1对象仍存在,它的消息循环仍阻塞在DispatchMessage()中,只有等消息框3、消息框2的消息循环退出、OnTimer()退出后,才会从DispatchMessage()返回,然后发现m_nFlags不需要继续运行消息框了,于是退出消息循环,然后退出消息框对象,然后退出OnTimer(),消息循环就又交还给MFC程序的主循环了。
四、非模式对话框
非模对话框没有自己的消息循环,也不会使父窗失效。
用Create()来显示非模对话框,函数会立刻返回,所以必须要用new,这样对话框对象就保留在内存中。
如果用CAboutDlg dlgAbout;,则处理函数结束后,对象也被销毁,即对话框一显示就被关掉了。
CAboutDlg* pdlg = new CAboutDlg;
pdlg ->Create(IDD_ABOUTBOX); //非模式对话框,函数立刻返回,无自己的消息循环
pdlg->ShowWindow(SW_SHOW);
所以,关键是消息循环、消息处理函数重入。