模态对话框和非模态对话框的消息循环分析

2023-05-16

1、非模态对话框和父窗口共享当前线程的消息循环

2、模态对话框新建一个新的消息循环,并由当前消息循环派发消息,而父窗口。模态对话框屏蔽了用户对它父窗口的操作,但是不是在消息循环里面屏蔽,所以给父窗口发送消息,父窗口还是可以接收得到。

3、调用模态对话框的窗口处理函数会被阻塞,但是新的消息循环仍然可以调用父窗口的消息处理函数,所以,发送给父窗口的新消息仍然可以被及时处理。

/*****************************MFC模态对话框的消息循环***************************/

MFC模态对话框的消息循环
单线程程序, 当主窗口响应函数中弹出模态对话框时,为什么主窗口响应函数可能照常工作?

当弹出模态对话框时,线程的消息循环无法返回,父窗口的事件本应没人处理,应该处于卡死状态,但实事上父窗口是可以正常响应能接收到的消息的,比如计时器传来的WM_TIMER 及系统托盘菜单传回来的WM_COMMAND。

之前的消息循环无法返回是正确的,但模态对话框并不意味着死循环,实事上,它在做另一个消息循环。


AfxInternalPumpMessage() 里面就是一个消息泵,包括消息的获取与分发:

 

 


只要有消息循环存在线程内的所有窗口就会活过来。不管弹出多少个模态对话框,线程内始终有一个消息循环为所以的窗口服务。

但这种形式带来的那一个问题就是,如何关闭所有模态对话框并退出程序?

如果简单地有EndDialog来关闭对话框,是无法让所有的消息循环返回的。有一种做法是使用PostQuitMessage,使当前的消息循环退出,消息循环收到WM_QUIT后,不当退出本次循环,还会给线程消息队列Post另一个QUIT 消息,这样消息循环就接二连三挨个退出了。

int CWnd::RunModalLoop(DWORD dwFlags)中的代码片段可以说明这点:

 /*************************windows 消息队列,消息循环,模态对话框**************/

Windows的消息队列是基于线程的。


消息队列,消息循环:

线程是程序串行执行的最小单位。

一个典型的Win32项目(不是MFC项目,只有一个窗口的项目),其中的消息循环会使用如下代码实现:


//代码段1

MSG msg
BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}

GetMessage()是取得消息,如果没有消息,线程阻塞在这里,不占用CPU。

DispatchMessage()是将消息分发,分发到所有在这个线程中创建的窗口的窗口处理函数中去。 如果不需要分发消息,就不需要调用DispatchMessage(),例如线程中没有窗口的情况。


线程消息循环:

实际上,任何线程只要调用了上述代码段1中的代码,就已经实现了消息循环的功能。更进一步,其实只要调用GetMessage()函数就可以了。

即:

//代码段2

BOOL bRet;  MSG msg;
while( (bRet = GetMessage(&msg, NULL, 0, 0)) != 0)

if (bRet == -1 || WM_QUIT == msg.message){
// handle the error and possibly exit
break;
}
        }

在工作线程中,只要有代码段2,就可以实现消息循环的功能了。

这是一个很方便的线程间异步通信的机制。给线程发消息用PostThreadMessage()。为什么去掉了DispatchMessage()呢,因为一般情况下,在工作线程中是不需要创建窗口的,不需要分发到窗口中去处理,能从线程的消息队列里取得消息就足够了。

如果设计线程的初衷为:当某条件发生时,让工作线程开始工作,一直将工作完成,之后挂起,等待新条件的发生,等待时不占用CPU资源。 那么代码段2所展示的机制就能很好的完成这样的功能。 而且可以使多个工作线程都用同样的机制实现,彼此间协同工作,加之某种资源共享机制,可以实现一个异步的消息处理链。


模态对话框:

消息循环可以有多个,可以在上一级消息循环的某个消息的处理过程中,局部创建一个消息循环,模态对话框就是采用这种机制创建出来的。

一个线程可以有多个消息循环,并行的消息循环显然没有意义,多个就是在消息循环中嵌套消息循环。

如代码段1中的DispatchMessage函数,将消息派发到窗口的消息处理函数中,WndProc1()。

现在假设在WndProc1()中创建一个消息循环,并且只取得这个窗口本身的消息 GetMessage(&msg, hWnd, 0, 0),不必Dispatch了,因为已经找到了目标窗口,然后用这个窗口真正的消息处理函数去处理。 这样模态对话框存在时,处理了所有的窗口消息,创建模态对话框的窗口就一直得不到处理消息的机会,模块对话框就始终处于最上层,直到其主动退出。

当然,对模态对话框的这种解释是简化了的,实际过程可能复杂的多,取得的窗口消息至少要包含所有的子窗口的消息,也需要Dispatch到相应的子窗口,等等。但是最基本的机制应该就是这样的。
/*********************模态对话框的消息机制****************************/

当一个窗口时模态对话框时,它的消息处理过程是有点特殊的。模式对话框都有自己的消息循环,它阻塞的是原始的消息循环,但是被对话框的消息循环接替。消息循环的本

质是调用窗口过程,进一步调用你的各种消息响应函数,所以无论有多少个消息循环存在,只要有一个消息循环有效,所有的消息响应函数都能被调用,这也是为什么主窗口还能

响应消息的缘故。多个消息循环的存在会产生某些副作用,比如消息重入,第一次消息响应时弹出一个模式对话框,模式对话框的消息循环2取代原始消息循环1,假设此时主窗口

消息队列里又有一个同样的消息到来,消息循环2也会调用同样的窗口过程(响应函数),此时就能导致消息重入(因为第一次进入这个处理函数还没有返回,本质上这个响应函数

被递归调用了),这也是能弹出多个模式对话框的原因。每个模式对话框都有自己的消息循环,只有最后一个弹出对话框的消息循环才是活动的消息循环,其它所有消息循环(包

括主窗口、之前弹出的模式对话框)全被阻塞。这里的“阻塞”并不是消息被阻塞,而只是DispatchMessage一直没有返回而已,但是其它的消息循环会接替这些工作。

/**********************MFC模态模式对话框,MessageBox消息循环原理,定时器TIMER消息响应处理函数重入************/

一、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);

所以,关键是消息循环、消息处理函数重入。

/*****************模态对话框和非模态对话框的区别*********************/


模态对话框 操作模式上来讲 模态对话框在关闭对话框(OnOk,OnCancel,OnClose)这三个消息产生之前不可对此对话框以外的对话框进行操作  当上面3个消息产生后系统负责删除模态对话框资源
而非模态对话框可以进行其他操作 必须在三个消息发生后自己在析构函数里回收此对话框资源
比较麻烦
 
模态对话框用DoModal()可以负责产生,显示,销毁窗口
非模态对话框需要调用Create()然后在创建的时候WS_VISIBLE或者在创建都调用ShowWindow
进行显示  最后调用DestroyWindow()  然后自己删除掉对话框对象比较麻烦
  

按工作方式不同,可将对话框分成两类:
模式对话框(modal dialog box模态对话框):在关闭模式对话框之前,程序不能进行其他工作(如一般的“打开文件”对话框)
无模式对话框(modeless dialog box 非模态对话框):模式对话框打开后,程序仍然能够进行其他工作(如一般的“查找与替换”对话框)
两者的区别:
一. 非模态对话框的模板必须具有Visible风格(Visible=True),否则对话框将不可见,而模态对话框则无需设置该项风格。在实际编程中更加保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。
二. 非模态对话框对象是用new操作符来动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建的。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。
三. 通过调用CDialog::Create函数来启动对话框,而不是CDialog::DoModal,这是两者之间区别的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆栈中构建对话框对象,而不能以局部变量的形式来构建之。
四. 必须调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd::DestroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。
五. 因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,框架会调用CWnd::PostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下
void CModelessDialog::PostNcDestroy
{delete this; //删除对象}
这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者就不必显式地调用delete来删除对话框对象了。
六. 必须有一个标志表明非模态对话框是否打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。
例如:
创建模态对话框
CTestDlg dlg;
dlg.DoModal();
创建非模态对话框
CTestDlg * dlg = new CTestDlg;
dlg->Create(IDD_TEST_DLG);
dlg->ShowWindow(SW_SHOW); 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

模态对话框和非模态对话框的消息循环分析 的相关文章

随机推荐

  • 多线程下调用ShowDialog时异常原因及解决办法

    提示在可以调用OLE之前 xff0c 必须将当前线程设置为单线程单元 xff08 STA xff09 模式 xff0c 请确保您的Main函数带有STAThreadAttribute 导入导出功能 xff0c 在调用ShowDialog时的
  • C#操作注册表增删改查及关机能保存问题

    为何用程序写入注册表后 xff0c 重启机器注册表项就彻底丢失呢 仔细看看RegCreateKeyEx的用法 其中的dwOptions使用 REG OPTION NON VOLATILE 而不是 REG OPTION VOLATILE 重启
  • vnc远程,在windows下如何实现vnc远程

    在平时的工作中 xff0c 因为工作性质 xff0c 所以经常会用到vnc远程 xff0c 那有小伙伴知道如何在win下面实现vnc远程吗 xff1f 哪款工具能较好的实现vnc远程呢 xff1f 别着急 xff0c 咱今天就来看一下如何在
  • 函数形参传递概念及问题分析

    普通函数参数 下面程序试图改变main函数中a和b的值 include lt stdio h gt void fun int x int y int c c 61 a a 61 b b 61 c int main int a 61 1 b
  • c语言中函数调用的原理及形参传递实质原理分析

    一 函数参数传递机制的基本理论 函数参数传递机制问题在本质上是调用函数 xff08 过程 xff09 和被调用函数 xff08 过程 xff09 在调用发生时进行通信的方法问题 基本的参数传递机制有两种 xff1a 值传递和引用传递 以下讨
  • c++中多线程传递参数原理分析

    线程可以共享进程的内存空间 xff0c 线程拥有自己独立内存 关于参数的传递 xff0c std thread的构造函数只会单纯的复制传入的变量 xff0c 特别需要注意的是传递引用时 xff0c 传入的是值的副本 xff0c 也就是说子线
  • 线程的局部变量ThreadLocal概念

    ThreadLocal是什么 对这个词语分解 xff0c 将其分为Thread和Local xff0c 顾名思义便是本线程的变量 xff0c 既然是当前线程的变量 xff0c 那么就意味着这个变量对于其他线程来说就是隔离的 xff0c 也就
  • C++多线程并发中线程管理

    一 何为并发 刚开始接触计算机编程语言时 xff0c 我们编写一个程序 xff0c 在main入口函数中调用其它的函数 xff0c 计算机按我们设定的调用逻辑来执行指令获得结果 如果我们想在程序中完成多个任务 xff0c 可以将每个任务实现
  • 多线程中堆和栈区别的深入解析

    很多现代操作系统中 xff0c 一个进程的 xff08 虚 xff09 地址空间大小为4G xff0c 分为系统空间和用户空间两部分 xff0c 系统空间为所有进程共享 xff0c 而用户空间是独立的 xff0c 一般WINDOWS进程的用
  • C语言中队列、堆栈、内存映射、多线程概念

    队列 xff1a 先近先出 xff1b 栈 xff1a 先近后出 xff1b 栈的大小是由编译器决定的 xff0c 默认大小是1M xff0c 可以更改 xff0c 但是一般不建议修改 xff0c 每个exe都有一个栈 xff0c 无法利用
  • C++多线程编程分析-线程间通信

    上文我们介绍了如何建立一个简单的多线程程序 xff0c 多线程之间不可避免的需要进行通信 相比于进程间通信来说 xff0c 线程间通信无疑是相对比较简单的 首先我们来看看最简单的方法 xff0c 那就是使用全局变量 xff08 静态变量也可
  • 多线程访问全局变量和局部变量剖析

    如果一个变量是成员变量 xff0c 那么多个线程对同一个对象的成员变量进行操作时 xff0c 它们对该成员变量是彼此影响的 xff0c 也就是说一个线程对成员变量的改变会影响到另一个线程 如果一个变量是局部变量 xff0c 那么每个线程都会
  • c语言中全局变量多线程调用-局部变量、静态局部变量、全局变量与静态全局变量分析

    基本概念 xff1a 作用域 xff1a 起作用的区域 xff0c 也就是可以工作的范围 代码块 xff1a 所谓代码块 xff0c 就是用 括起来的一段代码 数据段 xff1a 数据段存的是数 xff0c 像全局变量就是存在数据段的 代码
  • vnc viewer 绿色版,6款超好用的vnc viewer 绿色版

    市面上形形色色的vnc viewer 绿色版软件很多 在众多的vnc viewer 绿色版软件中 你会选择哪一款呢 你所了解的vnc viewer 绿色版又有哪些呢 接下来让我们一起看看有哪些好用的vnc viewer 绿色版软件吧 第一款
  • django 框架模型之models常用的Field,这些Field的参数、及常见错误原因及处理方案。

    1 django 模型models 常用字段 1 models AutoField 自增列 61 int 11 如果没有的话 xff0c 默认会生成一个名称为 id 的列 如果要显式的自定义一个自增列 xff0c 必须设置primary k
  • 干货丨ChatGPT大爆发以来,最值得收藏的30个AI工具,让你生产力爆表、效率无敌!

    随着ChatGPT的火爆出圈 xff0c 炸出来一堆 AI神器 xff0c 它们不仅大大拓宽了我们原本的能力范围 xff0c 更是让工作效率瞬间翻倍 接下来 xff0c 给大家推荐30个精选的 AI工具 xff0c 拿走 xff0c 直接用
  • 多线程下局部变量与全局变量的使用及区别

    局部变量是在栈中运行 每个运行的线程都有自己的堆栈 别的线程无法访问得到 xff0c 因此我们说 xff0c 局部变量是 安全 的 全局变量是在堆中运行 堆是对所有的线程都可见的 因此在两个以上的线程访问全局变量时 xff0c 就会出现所谓
  • MFC中Windows窗口消息循环及多线程之间关系

    Windows中一个进程可以包含多个线程 xff0c 由多个线程组成 在Windows应用程序中 xff0c 窗体是由 UI线程 xff08 User Interface Thread xff09 的特殊类型的线程创建的 一个UI线程包含一
  • c#中Show和Showdialog的区别分析

    简单地说他们的区别就是show弹出来的窗体和父窗体 xff08 上一个窗体的简称 xff09 是属于同一等级的 xff0c 这两个窗体可以同时存在而且可以随意切换 xff0c 但是showdialog弹出来的窗体就不能这样 xff0c 他永
  • 模态对话框和非模态对话框的消息循环分析

    1 非模态对话框和父窗口共享当前线程的消息循环 2 模态对话框新建一个新的消息循环 xff0c 并由当前消息循环派发消息 xff0c 而父窗口 模态对话框屏蔽了用户对它父窗口的操作 xff0c 但是不是在消息循环里面屏蔽 xff0c 所以给