对话框(Dialog)是应用中常用的界面形态。一个典型的对话框的风格如下:
普通风格:WS_CAPTION | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_SYSMENU
扩展风格:WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW
Dialog从消息处理的角度又可以分为模态和非模态对话框。其中,模态对话框实际是调用了CWnd的RunModalLoop方法(代码位置:%Program Files%/Microsoft Visual Studio .NET 2003/Vc7/atlmfc/src/mfc/dlgcore.cpp)。该方法的核心为一个消息处理循环,这个过程同CWinThread所封装的消息循环类似,使用了PeekMessage非阻塞地获取消息,可分为两个阶段:
第一阶段:空闲状态处理。此时,消息队列中没有任何消息,此时无法退出模态循环。应用可以通过一些标志(Flag)来控制是否产生空闲消息,这些标志如下:
1. MLF_SHOWONIDLE:在空闲时,如果窗口不可见则让其可见
2. MLF_NOIDLEMSG:不产生WM_ENTERIDLE消息
3. MLF_NOKICKIDLE:不产生WM_KICKIDLE消息
注:空闲消息主要指WM_ENTERIDLE和WM_KICKIDLE。
第二阶段:非空闲消息处理。此时,可以通过调用EndModalLoop来结束模态循环,但该方法并非直接结束循环,而只是设置一个标志(WF_CONTINUEMODAL),等到消息队列处理完当前消息后,模态循环判断该标志决定是否退出。消息队列循环的主要由AfxPumpMessage方法封装,它封装了Windows上典型的消息循环,但是在Translate Message前增加了下面的判断:
pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)
其中,pState为线程数据结构,存储UI线程的一些数据,尤其是消息相关的数据。前一个条件表示不处理WM_KICKIDLE消息,后一个条件提供给了应用程序一个机会,便于对消息的过滤(CWnd的PreTranslateMessage虚方法)。
既然模态对话框调用了CWnd的方法,因此,其它类型的窗口也可以调用RunModalLoop让自己进入模态,即:模态并非对话框的专利。
CWinThread的消息循环:
代码位置:CWinThread的Run方法。%Program Files%/Microsoft Visual Studio .NET 2003/Vc7/atlmfc/src/mfc/thrdcore.cpp
在CWnd类中,提供了BeginModalState和EndModalState两个方法,实际上就是分别让该窗口Enable和Disable(使用EnableWindow API方法)。对于CDialog,调用DoModal使其呈现模态。本质上,该对话框仍然是非模态(普通)窗体,只是其父窗口被Disable了。DoModal做了如下事情:
1. 依据resource template创建对话框。由于不是使用CreateWindowEx,所以这之前也需要hook消息,以便消息能进入到该窗口的WndProc方法中。
2. 使父窗口Disable
3. 调用基类的RunModalLoop,进入模态循环
4. 调用EndModalLoop,设置退出标志,然后发送一个WM_NULL消息到队列中,退出模态循环
5. 恢复父窗口状态(Enable,且SetActive)
6. 销毁窗口
7. Unhook消息
8. Unlock并free前面的资源
对于非模态,就是普通的窗口形态,即:通常使用ShowWindow即可,复杂一些可以使用SetWindowPos方法。
如果有如下的需求,该如何去做呢?有一个模态和一个非模态对话框,两者的父窗口均为主Frame窗口。当模态对话框活动时,需要可以操作非模态窗口。
当模态窗口显示时,主Frame窗口已经被Disable。此时,作为其子窗口的非模态窗口也随之被Disable,也就是说也无法操作它。其实,既然它被Disable了,就只需要调用EnableWindow将其Enable就可以了。
前面已经提到了模态窗口的消息循环。此时,它和CWinThread所封装的消息循环共用同一个消息队列(既然都在一个线程中),且在核心消息的获取和分发上,两者是相同的(AfxPumpMessage方法),不同的主要只是多了对模态的管理。所以,如果此时从该模态窗口发送一个消息到父窗口,那么父窗口是可以接收到消息的,而不会因为窗口被Disable了,就无法接收消息了。如果一个窗口调用EnableWindow被Disable了,只是意味着它无法接收用户的键盘和鼠标消息,其它消息仍然可以接收到。注意,这里说的是用户触发的消息,也就是用户的行为导致系统触发的消息,此时,父窗口无法接收到。如果应用自己发送一个这类消息,那么同其它消息一样,是可以被接收到的。
(未完,待续......)