使用Windows API实现模态窗口

所谓模态窗口(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); 
}

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平台本身也是使用了这样的模式,不过比这个要复杂一些。

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

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

你可能感兴趣的:(使用Windows API实现模态窗口)