而对于非模态对话框,CreateDialog函数在创建对话框后直接返回,对话框的窗口的消息是通过用户程序中的消息循环派送的,即上图中的Windows内的内建消息循环被移到左边矩形框内。由用户自定义。
模态对话框
我们根据子窗口控件(二)中的程序来简单的进行扩展,增加一个关于对话框,我们首先要为程序添加一个菜单。然后新建一个对话框资源,然后仿照记事本的关于对话框来添加控件。如下所示:
其中,对话框中的图片和图标都是通过一个图片控件进行插入,我们在图片控件Type中选择Icon或Bitmap,之后再Image中选择要插入的图标和位图的ID即可。中间的一个横线也是一个图片控件,我们把图片控件拉成一条线后,在Sunken项中选择True。默认对话框有ok和cancel两个按钮,我们删除了cancel按钮。
其中窗口过程函数部分如下:
case ID_HELP_ABOUT:
DialogBox( (HINSTANCE)GetWindowLong( hwnd, GWL_HINSTANCE), MAKEINTRESOURCE(IDD_HELP_ABOUT), hwnd, AboutDlgProc );
对话框的窗口过程如下所示:
BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG :
return TRUE ;
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDOK :
case IDCANCEL :
EndDialog (hDlg, 0) ;
return TRUE ;
}
break ;
}
return FALSE ;
}
我们可以看到,尽管对话框的处理函数和窗口处理函数非常类似,但也有着许多区别:
1、窗口消息处理程序返回LRESULT,对话框返回一个BOOL。
2、如果窗口消息处理程序不处理某个特定的消息,那么它调用DefWindowProc函数,如果对话框程序处理一个消息,那么返回TRUE,如果不处理,则返回FALSE。
3、对话框程序不需要处理WM_PAINT或WM_DESTROY消息。对话框程序不接受WM_CREATE消息,而是在特殊的WM_INITDIALOG消息处理期间,对话框程序执行初始化操作。
WM_INITDIALOG消息是对话框接收到的第一个消息,这个消息只发送给对话框程序。如果对话框程序返回TRUE,那么Windows将输入焦点设定给对话框中第一个具有WS_TABSTOP样式(控件的TABSTOP属性设置为TRUE)的子窗口控件。
清除对话框使用EndDialog函数,这个函数的第二个参数的值,是返回给DialogBox的值。
在默认的对话框上有两个按钮,即OK和Cancel,OK按钮的ID值为IDOK,Cancel按钮的ID值为IDCANCEL。当我们点击Enter键的时候,默认为点击IDOK按钮。
非模态对话框
我们已经了解了模态对话框,知道模态对话框不允许我们切换到同一程序的另一个窗口。非模态对话框允许使用者在对话框与其他窗口之间进行切换。我们经常看到的非模态对话框有文档编辑程序中的查找和替换对话框。
要创建一个非模态对话框我们使用CreateDialog函数来创建。这个函数会返回对话框的窗口句柄。之前已经讲过,非模态对话框要求我们自己建立消息循环。也就是说非模态对话框的消息要经过程序的消息队列,而我们通过窗口处理程序中的消息循环从消息队列中获得消息。所以我们应该改动一下我们程序中的消息循环,以确定从消息队列中取得的是对话框消息还是窗口消息。
所以我们一般加一个判断,如下所示:
while(GetMessage(&msg, NULL, 0, 0))
{
if( hDlgModeless == 0 || !IsDialogMessage(hDlgModeless, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
之所以要判断hDlgModeless是否为0,是为了保证不会使用无效的窗口句柄来呼叫IsDialogMessage。另外需要注意的是我们使用DestroyWindow函数而不是EndDialog函数来结束非模态对话框。对于非模态对话框就简单介绍这么多,下面来看一下另外一类对话框。
通用对话框
有些对话框,在我们的编程过程中是经常需要使用的,比如文件打开对话框,保存对话框,查找替换对话框等等,Windows为这些对话框给我们提供了一种简单的方法。我们只需要初始化某一结构的各个字段,然后将这个结构的指针传送给某个函数,这个函数就会建立并显示对应的对话框。我们通过继续给上面的代码示例添加功能来说明几种常用的通用控件。
我们以打开对话框为例,打开对话框是一个通用对话框,我们要向调用这个通用对话框,需要使用GetOpenFileName函数,这个函数要求我们来传送一个OPENFILENAME结构体的指针,这个结构体中定义了初始化这个对话框的信息。OPENFILENAME定义如下:
主要的参数字段含义如下:
lpstrFilter---指定文件名筛选字符串,该字段决定了对话框中“文件类型”下拉式列表框中的内容,字符串可以由多组内容组成,每组包括一个说明字符串和一个筛选字符串,字符串的最后用两个0结束。
lpstrFile---指向一个包含文件名的缓冲区。如果这个缓冲区中已经包含了一个文件名,那么对话框初始化的时候将显示这个文件名。当用户选择了一个文件的时候,函数在这里返回新的文件名。
nMaxFile---指定lpstrFile参数指向的缓冲区长度。
lpstrFileTitle---指向一个缓冲区,用来接受用户选择的不含路径的文件名。这个字段可以为NULL。
nMaxFileTitle---指明lpstrFileTitle参数指向的缓冲区长度。
lpstrInitialDir---对话框初始化目录,可以为NULL
lpstrTitle---指向自定义的对话框标题,如果这个字段为NULL,那么“打开”对话框和“保存”对话框的默认标题是“打开”和“另存为”。
nFileOffset---返回文件名字符串中文件名的起始位置,如当用户选择了文件“c:\dir1\file.ext”时,这里将返回8;
nFileExtension---返回文件名字符串中扩展名的起始位置,同样是上面的字符串,这里返回13。
lpstrDefExt---指定默认扩展名,如果用户输入了一个没有扩展名的文件名,那么函数将会自动加上这个默认扩展名。
Falgs---该标志字段决定了对话框的不同行为,它可以使一些取值的组合,下面是一些比较重要的标志。
OFN_ALLOWMULTISELECT:允许同时选择多个文件名
OFN_CREATEPROMPT:如果用户输入了一个不存在的文件名,对话框向用户提问“是否建立文件”。
OFN_FILEMUSTEXIST:用户只能选择一个已经存在的文件名,使用这个标志的时候必须同时使用OFN_PATHMUSTEXIST标志
OFN_HIDEREADONLY:对话框中不显示“以只读方式打开”复选框
OFN_OVERWRITEPROMPT:在“保存”文件对话框中使用的时候,当选择一个已存在的文件时,对话框会提问“是否覆盖文件”。
OFN_PATHMUSTEXIST:用户输入文件名时,路径必须存在。
OFN_READONLY:对话框中的“以只读方式打开”复选框初始化的时候处于选中状态。
说了这么多,我们来看一个在我们的程序中添加一个打开对话框的例子,其中窗口处理函数如下:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static HWND hwndEdit ;
static OPENFILENAME ofn = {0};
static TCHAR szFile[260];
static TCHAR szFileName[20];
HANDLE hFile = NULL;
int iFileLength,iUniTest,i ;
DWORD dwBytesRead;
PBYTE pBuffer,pText,pConv;
BYTE bySwap;
switch (message)
{
case WM_CREATE :
hwndEdit = CreateWindow (TEXT ("edit"), NULL,
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
ES_LEFT | ES_MULTILINE |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0, hwnd, (HMENU) ID_EDIT,
((LPCREATESTRUCT) lParam) -> hInstance, NULL) ;
ZeroMemory( &ofn, sizeof(OPENFILENAME) );
ofn.lStructSize = sizeof( OPENFILENAME );
ofn.hwndOwner = hwnd;
ofn.hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
ofn.lpstrFilter = TEXT("文本文档(*.txt)\0*.txt\0所有文件(*.*)\0*.*\0\0");
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = szFileName;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrInitialDir = NULL;
ofn.lpstrDefExt = TEXT("txt");
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
return 0 ;
case WM_SETFOCUS :
SetFocus (hwndEdit) ;
return 0 ;
case WM_SIZE :
MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ;
return 0 ;
case WM_COMMAND :
switch( LOWORD( wParam ) )
{
case ID_EDIT:
if (HIWORD (wParam) == EN_ERRSPACE || HIWORD (wParam) == EN_MAXTEXT)
MessageBox (hwnd, TEXT ("Edit control out of space."), szAppName, MB_OK | MB_ICONSTOP) ;
return 0;
case IDM_FILE_OPEN:
if( GetOpenFileName( &ofn ) == TRUE )
{
// 下面内容涉及到文件操作知识,下篇文章详细讲解
if( INVALID_HANDLE_VALUE==
(hFile = CreateFile( szFile, GENERIC_READ | GENERIC_WRITE, 0,
(LPSECURITY_ATTRIBUTES) NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL )))
{
MessageBox( hwnd, TEXT("打开文件失败!"), TEXT("Error"), MB_OK | MB_ICONERROR );
return 0;
}
}
else
{
return 0;
}
iFileLength = GetFileSize( hFile, NULL );
pBuffer = (BYTE *)malloc( iFileLength + 2 );
ReadFile( hFile, pBuffer, iFileLength, &dwBytesRead, NULL );
CloseHandle( hFile );
pBuffer[iFileLength] = '\0';
pBuffer[iFileLength + 1] = '\0';
// Test to see if the text is Unicode(涉及编码问题,不细讲,有兴趣的可以查找这方面内容)
iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE ;
if (IsTextUnicode (pBuffer, iFileLength, &iUniTest))
{
pText = pBuffer + 2 ;
iFileLength -= 2 ;
if (iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE)
{
for (i = 0 ; i < iFileLength / 2 ; i++)
{
bySwap = ((BYTE *) pText) [2 * i] ;
((BYTE *) pText) [2 * i] = ((BYTE *) pText) [2 * i + 1] ;
((BYTE *) pText) [2 * i + 1] = bySwap ;
}
}
// Allocate memory for possibly converted string
pConv =(PBYTE) malloc (iFileLength + 2) ;
// If the edit control is not Unicode, convert Unicode text to
// non-Unicode (i.e., in general, wide character).
#ifndef UNICODE
WideCharToMultiByte (CP_ACP, 0, (PWSTR) pText, -1, pConv,
iFileLength + 2, NULL, NULL) ;
// If the edit control is Unicode, just copy the string
#else
lstrcpy ((PTSTR) pConv, (PTSTR) pText) ;
#endif
}
else // the file is not Unicode
{
pText = pBuffer ;
// Allocate memory for possibly converted string.
pConv = (PBYTE)malloc (2 * iFileLength + 2) ;
// If the edit control is Unicode, convert ASCII text.
#ifdef UNICODE
MultiByteToWideChar (CP_ACP, 0, (LPCSTR)pText, -1, (PTSTR) pConv,
iFileLength + 1) ;
// If not, just copy buffer
#else
lstrcpy ((PTSTR) pConv, (PTSTR) pText) ;
#endif
}
SetWindowText( hwndEdit, (PTSTR) pConv );
free( pBuffer );
free( pConv );
return 0;
case ID_HELP_ABOUT:
DialogBox( (HINSTANCE)GetWindowLong( hwnd, GWL_HINSTANCE), MAKEINTRESOURCE(IDD_HELP_ABOUT), hwnd, AboutDlgProc );
return 0;
}
return 0 ;
case WM_DESTROY :
if(hFile != NULL)
CloseHandle( hFile );
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
我们可以根据添加打开对话框的思想继续给程序添加字体选择对话框(ChooseFont),保存对话框(GetSaveFileName)以及查找替换对话框(FindText,ReplaceText)等等来逐步完善我们的程序。