CWnd的派生类-3、CDialog类

CWnd的派生类-3、CDialog类

对话框与普通窗口的区别仅在于,对话框是通过对话框模板建立起来的。只需要一个以模板为实参的创建命令,如CDialog::Create(),就可以完成对话框窗口及其子控件的创建工作,所有创建细节都由对话框模板来指示。而对于普通窗口,窗口及其包含的子控件必须逐一创建,而且要指定窗口风格等详细参数。对话框是最基本的可视化编程方法,一个应用程序往往包含众多的对话框资源模板和封装类,而普通窗体(包括框架窗体)却寥寥无几。但对话框的使用,只是方便了窗体和控件的创建过程,其本质与普通窗体无任何区别。

下面并不准备陈述对话框的技术细节,只与读者讨论两个相关问题:一是模态对话框的消息循环,二是对话框的命令消息路由。

7.4  模态对话框的消息循环
模态对话框是程序中最常用的窗口,当调用对话框的DoModal()成员后,就创建了一个模态对话框。其特点是,除了这个对话框窗体外,几乎不能操作程序的其他部分。但如果此时已经打开了两个以上的主窗体,只能禁止模态对话框所在的主窗口及其子窗口,包括主窗口下属的弹出对话框,但不包括下属的重叠窗口和普通弹出窗口。即当模态对话框弹出时,禁止了它的父窗口及大部分兄弟窗口的操作;模态对话框关闭后,被禁用的窗口将恢复使用。

7.4.1  模态对话框的创建与模式循环
其实,“模态”并不是对话框的专利,模态特性是封装在CWnd中的。所以,如果采取与模态对话框相同的创建方法,普通窗体也可以是模态的。这个方法就是在创建窗体后,调用CWnd::RunModalLoop()模式循环函数。该函数与前面讲过的CWinThread::Run()非常相似,也是一个消息循环泵,而且CWnd:: RunModalLoop()的消息处理还要稍复杂一些。在学习这个模式循环函数之前,首先来了解模态对话框的创建与销毁过程。下面是对CDialog::DoModal()函数的简单缩写。

int CDialog::DoModal()

{  //装入对话框模板资源

HINSTANCE hInst = AfxGetResourceHandle();   

hDialogTemplate = LoadResource(hInst, hResource);

         if (lpDialogTemplate == NULL)

                  return -1;

         //在建立模态对话框之前,禁止父窗口的鼠标和键盘输入

         HWND hWndParent = PreModal();//取得父窗口句柄(一般是程序主窗口,如主框架)

         BOOL bEnableParent = FALSE;

         if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))

         {

//禁止父窗口也将间接地禁止父窗口的下属窗口,但不包括下属的重叠窗口和普通弹出窗口

                  ::EnableWindow(hWndParent, FALSE);

                  bEnableParent = TRUE;

         }

         //通过资源模板创建对话框及其子控件

         if (CreateDlgIndirect(lpDialogTemplate,       CWnd::FromHandle(hWndParent), hInst))

                  { //创建成功

                                   //进入模式循环

                                   DWORD dwFlags = MLF_SHOWONIDLE;

                                   VERIFY(RunModalLoop(dwFlags) == m_nModalResult);

//当用户选择IDOK或IDCANCEL时,模式循环退出,对话框将被销毁

                  }       

         if (bEnableParent)

                  ::EnableWindow(hWndParent, TRUE);//恢复父窗口的工作状态,间接地恢复其兄弟窗口

         if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)

                  ::SetActiveWindow(hWndParent);//激活父窗口

         //销毁该模式对话框

         DestroyWindow();

         return m_nModalResult;

}

从以上代码可知,在模态对话框创建之前,首先要将该程序的主窗口(也是该对话框未来的宿主窗口)禁止。这样,该主窗口以及主窗口下属的所有子窗口和弹出对话框都被禁止。然后调用CreateDlgIndirect()创建对话框。注意,因为该对话框是在禁止主窗口之后创建的,所以它是活动的;也就是说,当前主窗口及其下属的所有窗口中,除重叠窗口和普通弹出窗口外,只有它是活动的。这是模态对话框的特点。可见,只要在该对话框销毁时重新激活主窗口就可以了,至此,已经完成了模态对话框的创建工作。但阅读以上代码会发现,事情并不这么简单,在创建对话框后还需进入模式循环,对话框关闭后,模式循环才退出。模式循环究竟有什么作用呢?

其实,由RunModalLoop()实现的模态循环,并不是创建模态窗口或模态对话框的方式。如上所述,只要在对话框创建之前禁止主窗口,在对话框销毁时激活主窗口,在形式上就已经实现了所谓的模态对话框。模式循环是专为模态窗口设计的一个消息循环,这个消息循环完成UI线程消息循环(由CWinThread::Run()封装)的全部功能,同时为处理模态窗口的特殊消息,增加了必要的处理代码。当模态窗口创建后,就进入这个消息循环,其中的消息循环泵暂时代替了UI线程的消息循环泵,为所有的窗口提取并分发消息。但所有被禁止的窗口无法接收鼠标和键盘消息,除非使用PostMessage()命令。

下面讲解CWnd::RunModalLoop()是如何工作的。

/*******************形参dwFlags可以是下列值的组合*****************

MLF_NOIDLEMSG     当消息队列空闲时,不发送WM_ENTERIDLE消息给主窗口

MLF_NOKICKIDLE    当消息队列空闲时,不发送WM_KICKIDLE消息给当前模态窗口

MLF_SHOWONIDLE当消息队列空闲时,刷新显示当前对话框(仅一次)*/

int CWnd::RunModalLoop(DWORD dwFlags)

{

         ASSERT(::IsWindow(m_hWnd)); // window must be created

//m_nFlags标志当前对话框的状态,值WF_MODALLOOP标志已经进入模态

         ASSERT(!(m_nFlags & WF_MODALLOOP));

         //标志空闲处理入口的状态

         BOOL bIdle = TRUE;

         //连续处理WM_KICKIDLE消息的次数

         LONG lIdleCount = 0;

//空闲时是否刷新显示当前对话框(仅一次)

         BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);

         HWND hWndParent = ::GetParent(m_hWnd);

         //设置对话框状态标志

         m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);

         MSG* pMsg = &AfxGetThread()->m_msgCur;//取得存储当前消息的缓冲

         for (;;)

         {

                  ASSERT(ContinueModal());//检查是否错误地结束了模式循环

                  //循环1:用于调度空闲处理

                  while (bIdle &&

                          !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))

                  {

                          ASSERT(ContinueModal());

                          if (bShowIdle)

                          {        //显示刷新当前窗口

                                   ShowWindow(SW_SHOWNORMAL);

                                   UpdateWindow();

                                   bShowIdle = FALSE;

                          }

                          if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)

                          {

                                   //给父窗口发送WM_ENTERIDLE 消息

         ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_ hWnd);

                          }

                          //或关系,如果第一个条件不成立,执行第二个条件

                          if ((dwFlags & MLF_NOKICKIDLE) ||

                                   !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))

                          {        //可见,在模态对话框内,可以将WM_KICKIDLE消息作为空闲消息进行处理

                                   bIdle = FALSE;

                          }

                  }

                  //循环2:提取并分发消息 

                  do

                  {        ASSERT(ContinueModal());

                          // pump message, but quit on WM_QUIT

                          if (!AfxGetThread()->PumpMessage())

                          {        AfxPostQuitMessage(0);

                                   return-1;

                          }

                          //收到特殊消息,是否刷新显示该对话框

                          if (bShowIdle &&

                                   (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))

                          {        ShowWindow(SW_SHOWNORMAL);

                                   UpdateWindow();

                                   bShowIdle = FALSE;

                          }

                          if (!ContinueModal())  //可能是关闭当前对话框的消息,判断是否该结束模式循环

                                   goto ExitModal;

                          //根据刚刚处理的消息类型,判断是否应该在没有消息到来时立即进行空闲处理

                          if (AfxGetThread()->IsIdleMessage(pMsg))

                          {

         bIdle = TRUE;

                                   lIdleCount = 0;

                          }

                  } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));

         }

ExitModal: //用户已关闭对话框,结束模式循环

         m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL); //清空对话框的模态标志

         return m_nModalResult;     //返回对话框的关闭代码(如IDOK、IDCANCEL)

}

通过比较CWinThread::Run()与CWnd::RunModalLoop()两个消息循环的差异,不难发现后者为模态对话框做了哪些工作。模式循环既可以向父窗口发送WM_ENTERIDLE消息,也可以向当前窗口发送与空闲消息等同的WM_KICKIDLE消息,使得模态对话框有能力在空闲时完成一定的操作。同时允许刷新显示对话框。但注意,CWinThread::OnIdle()在模式循环中不被调用。

在对CWinThread::PumpMessage()的阐述中,曾经提及WM_KICKIDLE消息,它在消息泵中不被分发处理。所以,在模式循环中使用SendMessage()而不是PostMessage()发送该消息。WM_KICKIDLE消息像一个未公开的秘密,没有正式的文档说明,它在afxpriv.h头文件中定义。如果你的模态对话框需要空闲处理,应包含这个头文件,然后手工添加消息映射即可。

7.4.2  结束模式循环
阅读RunModalLoop()代码可知,当调用ContinueModal()返回FALSE时,模式循环结束。该函数只是检查m_nFlags状态标志。

BOOL CWnd::ContinueModal()

{

         return m_nFlags & WF_CONTINUEMODAL;

}

显然,当用户单击IDOK或IDCANCEL时,改变了成员m_nFlags的状态,使得循环结束。下面列出相关的几个成员函数:

void CDialog::OnOK()

{        if (!UpdateData(TRUE))

         {        return;

         } //以IDOK为结束代码

         EndDialog(IDOK);

}

void CDialog::OnCancel()

{        //以IDCANCEL为结束代码

         EndDialog(IDCANCEL);}

void CDialog::EndDialog(int nResult)

{

         ASSERT(::IsWindow(m_hWnd));

         if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))

//如果当前对话框是模态的,则结束模式循环

         EndModalLoop(nResult);

         ::EndDialog(m_hWnd, nResult);//调用API结束有关本对话框的系统处理

}

void CWnd::EndModalLoop(int nResult)

{       

ASSERT(::IsWindow(m_hWnd));

         //设置返回代码

         m_nModalResult = nResult;

         if (m_nFlags & WF_CONTINUEMODAL)

         {        //设置模式循环结束标志,发送空消息通知消息泵

                  m_nFlags &= ~WF_CONTINUEMODAL;

                  PostMessage(WM_NULL);

         } }

可见,只要在对话框中调用CDialog::EndDialog()就可以结束模式循环。但结束模式循环后,还必须调用DestroyWindow()销毁对话框,这个工作在DoModal()退出前已经完成。但如果使用CDialog::Create()创建了一个非模态对话框,就不得不在直接或间接调用EndDialog()关闭对话框后,亲自调用DestroyWindow()了。

7.4.3  创建普通的模态窗口
通过以上对模态对话框的学习,已经掌握了创建模态窗口的技术。如果需要一个普通的模态窗口,可以参考以下步骤进行操作。

(1)调用EnableWindow()禁止程序主窗口。如果当前存在多个主窗口,禁止与该模态窗口有所属关系的主窗口。

(2)使用CWnd::Create()等创建命令,创建该窗口。可以是弹出窗口,也可以是重叠窗口。

(3)调用模式循环函数RunModalLoop(DWORD dwFlags),根据实际需要设置实参。如果需要空闲处理,还须手工添加消息映射。

(4)当关闭窗口时调用EndModalLoop(int nResult),根据实际需要设置结束代码。

(5)激活主窗口,调用DestroyWindow()摧毁当前模态窗口。一定要确保在窗口销毁前已经结束了模式循环。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/110616.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Nginx面试题(总结最全面的面试题!!!)

    Nginx面试题(总结最全面的面试题!!!)文章目录什么是Nginx?为什么要用Nginx?为什么Nginx性能这么高?Nginx怎么处理请求的?什么是正向代理和反向代理?使用“反向代理服务器的优点是什么?Nginx的优缺点?Nginx应用场景?Nginx目录结构有哪些?Nginx配置文件nginx.conf有哪些属性模块?Nginx静态资源?如何用Nginx解决前端跨域问题?Nginx虚拟主机怎么配置?基于虚拟主机配置域名基于端口的虚拟主…

  • 初学嵌入式开发用什么开发板_minipcie接口定义

    初学嵌入式开发用什么开发板_minipcie接口定义上海域格MINIPCIE开发板使用1、开发板概述开发(评估)板是为客户提供模块开发调试(评估)的平台。帮助客户对模块快速完成测试、开发、评估、验证产品特性以及功能演示等。客户可以在没有制作PCB的情况下,就能完成熟悉模块功能,并DEMO出相关程序,缩短开发周期。开发板上是标准MINIPCIE接口,请务必配合特定模块的硬件手册和AT手册使用。2、功能描述2.1平面图2.2实物图开发板基本接口调试概述:支持1路5V直流输入接口(使用5V电源供电,可将电

  • K8s基本概念入门_k8s菜鸟教程

    K8s基本概念入门_k8s菜鸟教程序言    没等到风来,绵绵小雨,所以写个随笔,聊聊k8s的基本概念。    k8s是一个编排容器的工具,其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部署,应用提供服务,扩容缩容应用,应用更新,都非常的方便,而且可以做到故障自愈,例如一个服务器挂了,可以自动将这个服务器上的服务调度到另外一个主机上进行运行,无需进行人工干涉。那么,问题来了,要运维何用?    k8s可以更快的更新新版

    2022年10月22日
  • linux 启动nginx[通俗易懂]

    linux 启动nginx[通俗易懂]启动操作nginx-c/usr/local/nginx/conf/nginx.conf-c参数指定了要加载的nginx配置文件路径停止操作停止操作是通过向nginx进程发送信号来进行的步骤1:查询nginx主进程号ps-ef|grepnginx在进程列表里 面找master进程,它的编号就是主进程号了。步骤2:发送信号从容停止Nginx…

  • input标签checkbox选中触发事件的方法

    input标签checkbox选中触发事件的方法目的:1.打开页面时,根据后端返回的的值isRequired,设置页面的checkbox标签的勾选状态,并给隐藏的text标签的value赋值,以便于在提交页面时把isRequired再返回给后端2.切换checkbox标签的勾选状态时,修改隐藏的text标签的value的值,勾选是1,取消勾选是0html代码:<divclass=”col-sm-8″><inputtype=”checkbox”class=”minimalpull-leftGrandpaisR.

    2022年10月23日
  • smartselect是什么意思_Smart Connect

    smartselect是什么意思_Smart Connect一、什么是S.M.A.R.T.SMART是一种磁盘自我分析检测技术,早在90年代末就基本得到了普及每一块硬盘(包括IDE、SCSI)在运行的时候,都会将自身的若干参数记录下来这些参数包括型号、容量、温度、密度、扇区、寻道时间、传输、误码率等硬盘运行了几千小时后,很多内在的物理参数都会发生变化某一参数超过报警阈值,则说明硬盘接近损坏此时硬盘依然在工作,如果用户不理睬这个报警继续

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号