在本章开始,解释了对话框可以是“模态”或“非模态”的。上一节中,我们详细讲述了模态对话框。模态对话框(除了系统模态对话框外)允许用户在对话框和其他程序之间切换。但是,用户无法切换到同一程序的另一个窗口中,直到该模态对话框被销毁。非模态对话框则允许用户在对话框和窗口之间,以及在对话框和其他程序之间进行切换。非模态对话框因此更接近于你的程序产生的正常弹出窗口。
如果在一段时间内,一直要显示某个对话框,用户更倾向于使用非模态对话框,因为它更方便。举例来说,字处理程序经常使用非模态对话框来实现文本查找和修改对话框。如果查找对话框是一个模态对话框,用户将不得不从菜单中选择査找,输入想找的字符串,结束对话框以返回到文档,然后重复整个过程査找同一字符串的另一实例。允许在文档和对话框之间进行切换使用户的操作更方便。
使用DialogBox能产生模态对话框。只有对话框销毁后,函数才返回一个值。它返回的值是EndDialog的第二个参数所指定的,对话框过程使用该函数来终止对话框。调用CreateDialog可以创建非模态对话框。此函数的参数与DialogBox相同:
hDlgModeless = CreateDialog ( hInstance, szTemplate, hwndParent, DialogProc) ;
不同的是,CreateDialog函数会立即返回对话框的窗口句柄。通常,你把这个窗口句柄存储在一个全局变量中。
本节我们将介绍非模态对话框的使用方法。
本节必须掌握的知识点:
模态与非模态对话框的区别
第66练:非模态对话框中创建滚动条控件
第67练:十六进制计算器
10.2.1 模态与非模态对话框的区别
非模态对话框与模态对话框的原理是类似的,但也有一些重要的不同。
■首先,非模态对话框通常包括标题栏和系统菜单框。当你在VS中创建一个对话框时,这实际上是默认选项。非模态对话框模板的样式语句如下:
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE
标题栏和系统菜单允许用户使用鼠标或键盘把非模态对话框移动至其他地方。你通常不会为模态对话框提供一个标题栏和系统菜单,因为用户对它下面的窗口根本无法做任何事情。
■第二个显著差异:请注意,WS_VISIBLE样式被包含在我们的样式语句的例子中。在
VS中,请在Dialog 属性对话框的More Styles选项卡中选中此选项。如果你省略了 WS_VISIBLE,则必须在调用CreateDialog后调用ShowWindow显示对话框窗口:
hDlgModeless = CreateDialog ( . . . ) ;
ShowWindow (hDlgModeless, SW_SHOW) ;
如果你既不包括WS_VISIBLE也不调用ShowWindow,那么该非模态对话框将不会被显示。 掌握了模态对话框的程序员往往忽视这一特殊性,因此经常在第一次试图创建一个非模态对话框时遇到困难。
■第三个差异:与模态对话框和消息框的消息不同的是,非模态对话框的消息要进入你 程序的消息队列。而消息队列必须经过改动才能把这些消息传递给对话框窗口过程。具体做法如下:使用CreateDialog创建一个非模态对话框时,把该函数返回的对话框句柄保存到一个全局变量(例如hDlgModeless)。如下更改消息循环:
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
如果消息是针对非模态对话框的,IsDialogMessage就会将其发送到对话框窗口过程并返回 TRUE(非零),否则返回FALSE(0)。只有当hDlgModeless为0或者该消息不是给对话框的才应该调用TranslateMessage和DispatchMessage函数。如果对程序窗口还使用了键盘快捷键,那么消息循环看起来会像下面这样:
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg))
{
if (!TranslateAccelerator (hwnd, hAccel, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
}
因为全局变量初始化为0,所以hDlgModeless将保持为0直到对话框被创建为止,从而确保了IsDialogMessage不会在使用无效窗口句柄的情况下被调用。当你销毁非模态对话框时,你必须采取同样的预防措施,原因下面会有解释。
hDlgModeless变量也可以用在程序的其他地方,用来检测非模态对话框是否存在。例 如,当hDlgModeless不等于0时,程序中的其他窗口可以将消息发送到该对话框。
■最后的大区别:请使用DestroyWindow而不是EndDialog来结束非模态对话框。当你 调用DestroyWindow时,还要把hDlgModeless全局变量设置为NULL 。
用户习惯于从系统菜单中选择Close来终止非模态对话框。虽然Close选项是可用的, Windows内的对话框窗口过程并不处理WM_CLOSE消息。你必须在对话框过程中自己完成这个任务:
case WM_CLOSE :
DestroyWindow (hDlg) ;
hDlgModeless = NULL ;
break ;
【注意】这两个窗口句柄的区别:DestroyWindow中的hDlg参数是传递给对话框过程的参 数:hDlgModeless则是从CreateDialog返回的、用来在消息循环中进行检测的全局变量。
也可以允许用户使用按钮来关闭非模态对话框。对此应使用和WM_CLOSE消息相同的逻辑。任何对话框必须“返回”给创建它的窗口的信息,都可以存储在全局变量中。如果不希望使用全局变景,那么如前所述,可以使用CreateDialogParam来创建非模态对话框,并给它传递一个指向结构的指针。
图10-10 Windows内置的对话框代码
如上图所示,模态对话框的消息循环使用Windows系统内置的消息循环,包含一个自定义对话框窗口过程和系统内建的默认对话框窗口过程。非模态对话框则和主窗口过程共用同一个消息循环。同样也包含一个自定义对话框窗口过程和系统内建的默认对话框窗口过程。
10.2.2 第66练:非模态对话框中创建滚动条控件
/*------------------------------------------------------------------
066 WIN32 API 每日一练
第66个例子COLORS2.C:非模态对话框
CreateDialog函数
IsDialogMessage 函数
SetDlgItemInt函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK ColorScrDlg (HWND, UINT, WPARAM, LPARAM) ;
HWND hDlgModeless ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Colors2");
HWND hwnd;
…(略)
hwnd = CreateWindow(szAppName, TEXT("Color Scroll"),
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,//不擦除对话框的情况下重绘主窗口
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
//创建非模态对话框
hDlgModeless = CreateDialog(hInstance, TEXT("ColorScrDlg"),
hwnd, ColorScrDlg);
while (GetMessage(&msg, NULL, 0, 0))
{ //非模态对话框句柄为0或者不是非模态对话框消息时
if (hDlgModeless == 0 || !IsDialogMessage(hDlgModeless, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM
lParam)
{
switch (message)
{
case WM_DESTROY:
DeleteObject((HGDIOBJ)SetClassLong(hwnd, GCL_HBRBACKGROUND,//删除画刷
(LONG</