使用Windows API实现模态窗口

所谓模态窗口(modal window),又叫作模式窗口,通常是指应用程序中那些任务比较紧要的窗口。只要它们存在,它们便会阻止用户访问其余窗口(或者是阻止用户访问其祖先窗口)。在windows中,使用DialogBoxParam显示的对话框就是模态的。虽然模态对话框在windows中很广泛,可是并无SDK级别的API,能够将一个窗口变为模态显示。不过在同为微软提供的MFC/WTL框架中有这样的API——Domodal(),使用代码基本上是这个样子的:windows

CXXXDialog dlg(someWindow);
int nRet = dlg.Domodal();
if (nRet == IDOK) 
{ 
	//从dlg中获取想要的数据
} 
else 
{
	//用户取消了窗口 
}

这方法挺好用的,可是若是只是使用win32 api 怎么办?或者要让一个窗口(而不是对话框)模态显示又要怎么办?这个时候就须要咱们本身实现模态机制了。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); 
}

DoModal()将本身显示,并将父窗口禁用以后,便在探测g_isModaling的同时接管了消息循环,只要g_isModaling的值不为FALSE,消息循环就持续进行。当EndModal()被调用时,g_isModaling的值变为FALSE,DoModal()内消息循环检查到此时,退出消息循环,把父窗口禁用取消掉,隐藏本身而后退出。注意EndModal()第5行,给本身的消息队列发了条WM_NULL消息,这条消息自己正如它的字面意思同样,无任何意义,只是为了唤醒DoModal()内的GetMessage()。还有一点,就是GetMessage第二个参数是NULL而不是hWnd,由于此处消息要接管全部线程消息。基本都在这了,给出调用的示例代码。

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()式模态窗口的简易实现了,win32平台自己也是使用了这样的模式,不过比这个要复杂一些。

其实还有另外的方案,就是非阻塞模式的模态方案。
spa

非阻塞模态
所谓非阻塞模态,即不让代码阻塞到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建立模态对话框的功能,因此对网上的文章进行了简单整理。code