写下这篇文章,是因为我解决一个困扰了很久的问题:如何手动实现一个模态对话框?既然要手动实现,那就不能使用RC资源文件,也不能使用像 DialogBox 之类的函数了,就只能使用 CreateWindowEx 。需要手动实现模态的一个原因是:Windows封装的DialogBox内部作了太多细节处理,导致我们常规程序在封装消息分发时并不那么统一。
在学校乱搞一气学了三年Windows桌面开发的我终于明白了有 父窗口 和 所有(拥有)者窗口 的概念。
如果在创建一个窗口时,指定了 WS_CHILD 风格,同时要得指定 hParent(父窗口)。因为子窗口必定属于某个父窗口。这样的两个窗口是父子关系。子窗口可以通过 GetParent 来获取其父窗口(虽然 MSDN 上说明了 GetParent 是用于获取父窗口或所有者窗口的,但是在实际使用过程中貌似并不是那样)。
而如果在创建一个窗口时,未指定 WS_CHILD 风格,那就需要指定为 OVERLAPPEDWINDOW 或者 POPUPWINDOW,这两种窗口风格很相近。它们都属于 顶级窗口,也就是没有父窗口的窗口。但这并不是说明除该窗口的子窗口外它就与外界没了任何联系。还有一种 顶级窗口 与 顶级窗口 之间的联系,一种叫作 拥有者窗口(Owner)与被拥有者窗口(Owned) 的联系。
要实现模态窗口的前提就需要创建两个窗口之间的这种拥有与被拥有的关系。用代码来表现就是:
HWND WINAPI CreateWindowEx(
_In_ DWORD dwExStyle,
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ DWORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HINSTANCE hInstance,
_In_opt_ LPVOID lpParam
);
// 创建窗口并指定拥有者
HWND hwnd = CreateWindowEx(
// ...
WS_OVERLAPPEDWINDOW, // dwStyle, 或者 WS_POPUP,反正不能是 WS_CHILD
// ...
hWndOwner, // 由于不能是WS_CHILD,所以这里就不再是父子关系了,
// 而是拥有者与被拥有者的关系
// ...
);
// 禁用拥有者窗口达到模态效果
EnableWindow(hWndOwner, FALSE);
,这样一来,模态窗口就成功创建了。现在不能用 GetParent 来获取父窗口了(可以试一下,很可能获取到空),因为它根本就不叫父窗口了,而是叫作拥有者窗口。取而代之的是需要使用 GetWindow(hWnd, GW_OWNER) 来获得。
但是如何正确地关闭模态还需要一篇文章来作更多解释。详见:如何以正确的顺序禁用与使能窗口(Win32)