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()摧毁当前模态窗口。一定要确保在窗口销毁前已经结束了模式循环。

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 太极 免ROOT使用Xposed模块

    太极 免ROOT使用Xposed模块太极免ROOT使用Xposed模块什么是太极?能干什么?我这里就不说了,大家可以去关注虚拟框架公众号去了解一下,我这里只是讲解怎么用。一,下载太极最新版太极下载畅玩微信模块下载其他模块都可以在虚拟框架公众号中下载二,添加应用打开太极可以看到太极内核已激活,说明可以正常使用。点击右下角按钮展开可以看到创建应用,模块管理,下载模块,…

    2022年6月4日
    604
  • django项目配置使用elasticsearch搜索引擎

    django项目配置使用elasticsearch搜索引擎Elasticsearc 简称 ES 是一个基于 Lucene 实现的开源 分布式 Restful 的全文本搜索引擎 此外 它还是一个分布式实时文档存储 其中每个文档的每个 field 均是被索引的数据 且可被搜索 也是一个带实时分析功能的分布式搜索引擎 能够扩展至数以百计的节点实时处理 PB 级的数据 基本组件索引 index 文档容器 换句话说 索引是具有类似属性的文档的集合 类似新华字典的索引检索页 里面包含了关键词与词条的对应关系 并记录词条的位置 索引名必须使用小写字母 搜索

    2025年8月4日
    2
  • ReverseFind(‘\\‘)函数

    ReverseFind(‘\\‘)函数此函数表示从右开始寻找最后一个“\\”的,并返回从左开始数的索引地址。与之相对的是Find(””)函数,表示从左开始寻找第一个“\\”,并返回从左开始数的索引地址。

    2022年6月26日
    60
  • 做事的真正态度

    做事的真正态度

    2022年1月12日
    48
  • SuperMap 最佳路径分析流程

    SuperMap 最佳路径分析流程学SuperMap也有一段时间了,总结一下软件下载:请到超图技术资源中心:http://support.supermap.com.cn第一步:导入数据第二步:选择数据选择线的时候多选一点线,路径分析最重要的就是路第三步:构建二维网格设置二维网格第四步:测试最佳路径第五步:发布下载:supermap-iserver下载请到超图技术资源中心:http://support.sup…

    2022年8月24日
    12
  • SPEL表达式_什么是EL表达式

    SPEL表达式_什么是EL表达式前言最近在搞项目的自定义流程,主流的流程引擎flowable不能很好的支撑业务需求,再考虑到后期的拓展,部门经理说让自己搞一套。这里玩SpEL表达式是为了解决业务流向判断的[条件表达式]问题仿佛记得java是有自定义表达式的,昨儿翻阅书记目录却没有找到,可能是我记错了吧(如果有知道的朋友请留言)。那就直接用SpEL表达式吧,早上查阅了下网上的资料,下面这篇文章挺全的,遂转载一下(copy过来添加了锚点定位,方便以后查阅)8.1介绍8.2功能概述8.3使用Spring的表达接口表达式

    2025年11月1日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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