所谓模态窗口(modal window),又叫做模式窗口,一般是指应用程序中那些任务比较紧要的窗口。只要它们存在,它们便会阻止用户访问其他窗口(或者是阻止用户访问其祖先窗口)。在windows中,使用DialogBoxParam显示的对话框就是模态的。虽然模态对话框在windows中很普遍,但是并没有SDK级别的API,可以将一个窗口变为模态显示。不过在同为微软提供的MFC/WTL框架中有这样的API——Domodal(),使用代码基本上是这个样子的:
CXXXDialog dlg(someWindow); int nRet = dlg.Domodal(); if (nRet == IDOK) { //从dlg中获取想要的数据 } else { //用户取消了窗口 }
这方法挺好用的,但是如果只是使用win32 api 怎么办?或者要让一个窗口(而不是对话框)模态显示又要怎么办?这个时候就需要我们自己实现模态机制了。
我们知道windows系统是依靠windows消息分发与响应来驱动UI交互的。在单线程UI中,如果某个消息的处理函数占用了大量时间,其他消息就不能得到及时的处理(因为是单线程)。如果我们的Domodal()不做特殊处理,结果就会导致整个UI线程卡住(因为代码的执行会停在Domodal()内部,阻塞线程的消息分发),这显然是不满足我们要求的。既要代码停留在Domodal(),又要不阻塞Windows消息分发过程,有什么方法可以解决呢?答案就是接管消息循环,即在DoModal()内部,将线程的消息循环接管过来,自己维护,直到外部通知结束模态循环。下面给出一种实现:
阻塞式模态
int DoModal(HWND hWnd) { //标识处于模态状态中 g_isModaling = TRUE; //显示自己 ShowWindow(hWnd, SW_SHOW); BringWindowToTop(hWnd); //disable掉父窗口 HWND hParentWnd = GetParent(hWnd); while(hParentWnd != NULL) { EnableWindow(hParentWnd, FALSE); hParentWnd = GetParent(hParentWnd); } //接管消息循环 while(g_isModaling) { MSG msg; if (!GetMessage(&msg, NULL, 0, 0)) break; TranslateMessage(&msg); DispatchMessage(&msg); } //模态已经退出 //恢复父窗口的enable状态 hParentWnd = GetParent(hWnd); while (hParentWnd != NULL) { EnableWindow(hParentWnd, TRUE); hParentWnd = GetParent(hParentWnd); } //将自己隐藏 ShowWindow(hWnd, SW_HIDE); return g_nModalCode; } void EndModal(int nCode) { g_nModalCode = nCode; g_isModaling = FALSE; PostMessage(NULL, WM_NULL, 0, 0); }
HWND hNewWnd = CreateWindow(szWindowClass, MODAL_TITLE, WS_POPUP | WS_THICKFRAME, 100, 100, 100, 100, hWnd, NULL, hInst, NULL); int nCode = DoModal(hNewWnd); TCHAR buffer[MAX_PATH]; _stprintf_s(buffer, _T("Modal ReturnCode Is %d"), nCode); MessageBox(hWnd, buffer, _T("Notify"), MB_OK); DestroyWindow(hNewWnd);
其实还有另外的方案,就是非阻塞模式的模态方案。
非阻塞模态
所谓非阻塞模态,即不让代码阻塞到DoModal()的调用上,这样也就不需要接管消息循环了。但是这样要怎么来实现模态窗口呢?
其实前面说到,所谓模态窗口,即那些阻止用户访问其他窗口(或者其祖先窗口)的窗口,只要在这种窗口显示的时候将其余窗口(或父窗口)禁用掉,便可以达到此目的。只不过如果不是用阻塞式方案,模态窗口的创建和销毁就不能放在一个函数里面了。
void BecomeModal() { //显示自己 ShowWindow(m_hwnd, SW_SHOW); BringWindowToTop(m_hwnd); //disable掉父窗口 HWND hParentWnd = GetParent(m_hwnd); while(hParentWnd != NULL) { EnableWindow(hParentWnd, FALSE); hParentWnd = GetParent(hParentWnd); } } void EndModal() { HWND hParentWnd = GetParent(m_hwnd); while(hParentWnd != NULL) { EnableWindow(hParentWnd, TRUE); hParentWnd = GetParent(hParentWnd); } }
需要注意的是,CreateWindow的参数需要设置为WS_POPUP,如果设置为WS_CHILD,则模态窗口也无法操作了,刚好项目中需要使用API创建模态对话框的功能,所以对网上的文章进行了简单整理。