使用对话框

使用对话框显示信息并提示用户输入。 应用程序加载并初始化对话框,处理用户输入,并在用户完成任务时销毁对话框。 处理对话框的过程因对话框是模式的还是无模式的而有所不同。 模式对话框要求用户在激活应用程序中的另一个窗口之前关闭对话框。 但是,用户可以在不同的应用程序中激活窗口。 无模式对话框不需要用户立即响应。 它类似于包含控件的main窗口。

以下部分讨论如何使用这两种类型的对话框。

显示消息框

最简单的模式对话框形式是消息框。 大多数应用程序使用消息框向用户发出错误警告,并提示在发生错误后如何继续操作。 使用 MessageBoxMessageBoxEx 函数创建消息框,指定要显示的消息以及按钮的数量和类型。 系统将创建一个模式对话框,并提供其自己的对话框模板和过程。 用户关闭消息框后, MessageBoxMessageBoxEx 将返回一个值,该值标识用户选择用于关闭消息框的按钮。

在以下示例中,应用程序显示一个消息框,该消息框在出现错误条件后提示用户执行操作。 消息框显示描述错误条件及其解决方法的消息。 MB_YESNO样式指示 MessageBox 提供两个按钮,用户可以选择如何继续操作:

int DisplayConfirmSaveAsMessageBox()
{
    int msgboxID = MessageBox(
        NULL,
        L"temp.txt already exists.\nDo you want to replace it?",
        L"Confirm Save As",
        MB_ICONEXCLAMATION | MB_YESNO
    );

    if (msgboxID == IDYES)
    {
        // TODO: add code
    }

    return msgboxID;    
}

下图显示了上述代码示例的输出:

消息框

创建模式对话框

使用 DialogBox 函数创建模式对话框。 必须指定对话框模板资源的标识符或名称,以及指向对话框过程的指针。 DialogBox 函数加载模板,显示对话框,并处理所有用户输入,直到用户关闭对话框。

在以下示例中,当用户单击应用程序菜单中的“ 删除项 ”时,应用程序将显示一个模式对话框。 对话框包含编辑控件 (用户在其中输入项的名称) “ 确定 ”和“ 取消” 按钮。 这些控件的控件标识符分别为ID_ITEMNAME、IDOK 和 IDCANCEL。

该示例的第一部分由创建模式对话框的 语句组成。 当系统收到具有IDM_DELETEITEM菜单标识符的WM_COMMAND消息时,这些语句在应用程序的main窗口过程中创建对话框。 该示例的第二部分是对话框过程,该过程检索编辑控件的内容,并在收到 WM_COMMAND 消息时关闭对话框。

以下语句创建模式对话框。 对话框模板是应用程序可执行文件中的资源,其资源标识符DLG_DELETEITEM。

case WM_COMMAND: 
    switch (LOWORD(wParam)) 
    { 
        case IDM_DELETEITEM: 
            if (DialogBox(hinst, 
                          MAKEINTRESOURCE(DLG_DELETEITEM), 
                          hwnd, 
                          (DLGPROC)DeleteItemProc)==IDOK) 
            {
                // Complete the command; szItemName contains the 
                // name of the item to delete. 
            }

            else 
            {
                // Cancel the command. 
            } 
            break; 
    } 
    return 0L; 

在此示例中,应用程序将其main窗口指定为对话框的所有者窗口。 当系统最初显示对话框时,其位置相对于所有者窗口工作区的左上角。 应用程序使用 DialogBox 的返回值来确定是继续操作还是取消操作。 以下语句定义对话框过程。

char szItemName[80]; // receives name of item to delete. 
 
BOOL CALLBACK DeleteItemProc(HWND hwndDlg, 
                             UINT message, 
                             WPARAM wParam, 
                             LPARAM lParam) 
{ 
    switch (message) 
    { 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDOK: 
                    if (!GetDlgItemText(hwndDlg, ID_ITEMNAME, szItemName, 80)) 
                         *szItemName=0; 
 
                    // Fall through. 
 
                case IDCANCEL: 
                    EndDialog(hwndDlg, wParam); 
                    return TRUE; 
            } 
    } 
    return FALSE; 
} 

在此示例中,过程使用 GetDlgItemText 从由 ID_ITEMNAME 标识的编辑控件中检索当前文本。 然后,该过程调用 EndDialog 函数,根据收到的消息将对话框的返回值设置为 IDOK 或 IDCANCEL,并开始关闭对话框的过程。 IDOK 和 IDCANCEL 标识符对应于 “确定” 和“ 取消” 按钮。 该过程调用 EndDialog 后,系统会向过程发送其他消息以销毁对话框,并将对话框的返回值返回给创建对话框的函数。

创建无模式对话框

使用 CreateDialog 函数创建无模式对话框,指定对话框模板资源的标识符或名称以及指向对话框过程的指针。 CreateDialog 加载模板,创建对话框,并选择性地显示它。 应用程序负责检索用户输入消息并将其调度到对话框过程。

在以下示例中,当用户单击应用程序菜单中的“ 转到 ”时,应用程序将显示一个无模式对话框(如果尚未显示)。 对话框包含编辑控件、检查框以及“确定”和“取消”按钮。 对话框模板是应用程序可执行文件中的资源,其资源标识符DLG_GOTO。 用户在编辑控件中输入行号并检查检查框以指定行号相对于当前行。 控件标识符为ID_LINE、ID_ABSREL、IDOK 和 IDCANCEL。

示例第一部分中的 语句创建无模式对话框。 这些语句在应用程序的main窗口的窗口过程中,当窗口过程收到具有IDM_GOTO菜单标识符的WM_COMMAND消息时创建对话框,但前提是全局变量尚未包含有效的句柄。 示例的第二部分是应用程序的main消息循环。 循环包含 IsDialogMessage 函数,以确保用户可以在此无模式对话框中使用对话框键盘界面。 该示例的第三部分是对话框过程。 该过程检索编辑控件的内容,并在用户单击“确定”按钮时检查框。 当用户单击“ 取消 ”按钮时,该过程会销毁对话框。

HWND hwndGoto = NULL;  // Window handle of dialog box 
                
...

case WM_COMMAND: 
    switch (LOWORD(wParam)) 
    { 
        case IDM_GOTO: 
            if (!IsWindow(hwndGoto)) 
            { 
                hwndGoto = CreateDialog(hinst, 
                                        MAKEINTRESOURCE(DLG_GOTO), 
                                        hwnd, 
                                        (DLGPROC)GoToProc); 
                ShowWindow(hwndGoto, SW_SHOW); 
            } 
            break; 
    } 
    return 0L; 

在前面的语句中,仅当 不包含有效的窗口句柄时hwndGoto,才调用 CreateDialog。 这可确保应用程序不会同时显示两个对话框。 若要支持这种检查方法,对话框过程在销毁对话框时必须设置为 NULL

应用程序的消息循环由以下语句组成。

BOOL bRet;

while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) 
{ 
    if (bRet == -1)
    {
        // Handle the error and possibly exit
    }
    else if (!IsWindow(hwndGoto) || !IsDialogMessage(hwndGoto, &msg)) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
} 

循环检查对话框窗口句柄的有效性,并且仅在句柄有效时调用 IsDialogMessage 函数。 IsDialogMessage 仅处理消息(如果消息属于对话框)。 否则,它将返回 FALSE ,并且循环将消息调度到相应的窗口。

以下语句定义对话框过程。

int iLine;             // Receives line number.
BOOL fRelative;        // Receives check box status. 
 
BOOL CALLBACK GoToProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
    BOOL fError; 
 
    switch (message) 
    { 
        case WM_INITDIALOG: 
            CheckDlgButton(hwndDlg, ID_ABSREL, fRelative); 
            return TRUE; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDOK: 
                    fRelative = IsDlgButtonChecked(hwndDlg, ID_ABSREL); 
                    iLine = GetDlgItemInt(hwndDlg, ID_LINE, &fError, fRelative); 
                    if (fError) 
                    { 
                        MessageBox(hwndDlg, SZINVALIDNUMBER, SZGOTOERR, MB_OK); 
                        SendDlgItemMessage(hwndDlg, ID_LINE, EM_SETSEL, 0, -1L); 
                    } 
                    else 

                    // Notify the owner window to carry out the task. 
 
                    return TRUE; 
 
                case IDCANCEL: 
                    DestroyWindow(hwndDlg); 
                    hwndGoto = NULL; 
                    return TRUE; 
            } 
    } 
    return FALSE; 
} 

在前面的语句中, 过程处理 WM_INITDIALOGWM_COMMAND 消息。 在WM_INITDIALOG处理过程中,该过程通过将全局变量的当前值传递给 CheckDlgButton 来初始化检查框。 然后,该过程返回 TRUE 以指示系统设置默认输入焦点。

在WM_COMMAND处理过程中,仅当用户单击“取消”按钮(即具有 IDCANCEL 标识符的按钮)时,该过程才会关闭对话框。 该过程必须调用 DestroyWindow 以关闭无模式对话框。 请注意,该过程还将变量设置为 NULL ,以确保依赖于此变量的其他语句正常运行。

如果用户单击“确定”按钮,该过程将检索检查框的当前状态并将其分配给 fRelative 变量。 然后,它使用 变量从编辑控件中检索行号。 GetDlgItemInt 将编辑控件中的文本转换为整数。 fRelative 的值确定函数将数字解释为有符号值还是无符号值。 如果编辑控件文本不是有效数字, GetDlgItemInt 会将 fError 变量的值设置为非零值。 该过程检查此值以确定是显示错误消息还是执行任务。 发生错误时,对话框过程会向编辑控件发送一条消息,指示该控件选择控件中的文本,以便用户可以轻松替换它。 如果 GetDlgItemInt 未返回错误,则该过程可以自行执行请求的任务,也可以向所有者窗口发送消息,指示其执行操作。

初始化对话框

在处理 WM_INITDIALOG 消息时初始化对话框及其内容。 最常见的任务是初始化控件以反映当前对话框设置。 另一个常见任务是在屏幕上或其所有者窗口中居中设置对话框。 某些对话框的一项有用任务是将输入焦点设置为指定的控件,而不是接受默认输入焦点。

在以下示例中,对话框过程在处理 WM_INITDIALOG 消息时将对话框居中并设置输入焦点。 为了居中对话框,该过程检索对话框和所有者窗口的窗口矩形,并计算对话框的新位置。 若要设置输入焦点,该过程会检查 wParam 参数以确定默认输入焦点的标识符。

HWND hwndOwner; 
RECT rc, rcDlg, rcOwner; 

....
 
case WM_INITDIALOG: 

    // Get the owner window and dialog box rectangles. 

    if ((hwndOwner = GetParent(hwndDlg)) == NULL) 
    {
        hwndOwner = GetDesktopWindow(); 
    }

    GetWindowRect(hwndOwner, &rcOwner); 
    GetWindowRect(hwndDlg, &rcDlg); 
    CopyRect(&rc, &rcOwner); 

    // Offset the owner and dialog box rectangles so that right and bottom 
    // values represent the width and height, and then offset the owner again 
    // to discard space taken up by the dialog box. 

    OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); 
    OffsetRect(&rc, -rc.left, -rc.top); 
    OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); 

    // The new position is the sum of half the remaining space and the owner's 
    // original position. 

    SetWindowPos(hwndDlg, 
                 HWND_TOP, 
                 rcOwner.left + (rc.right / 2), 
                 rcOwner.top + (rc.bottom / 2), 
                 0, 0,          // Ignores size arguments. 
                 SWP_NOSIZE); 

    if (GetDlgCtrlID((HWND) wParam) != ID_ITEMNAME) 
    { 
        SetFocus(GetDlgItem(hwndDlg, ID_ITEMNAME)); 
        return FALSE; 
    } 
    return TRUE; 

在上述语句中,该过程使用 GetParent 函数检索对话框的所有者窗口句柄。 函数将所有者窗口句柄返回到对话框,并将父窗口句柄返回到子窗口。 由于应用程序可以创建没有所有者的对话框,因此该过程会检查返回的句柄,并在必要时使用 GetDesktopWindow 函数检索桌面窗口句柄。 计算新位置后,该过程使用 SetWindowPos 函数移动对话框,指定HWND_TOP值以确保对话框保留在所有者窗口的顶部。

在设置输入焦点之前,该过程会检查默认输入焦点的控件标识符。 系统传递 wParam 参数中默认输入焦点的窗口句柄。 GetDlgCtrlID 函数返回由窗口句柄标识的控件的标识符。 如果标识符与正确的标识符不匹配,该过程将使用 SetFocus 函数来设置输入焦点。 需要 GetDlgItem 函数来检索所需控件的窗口句柄。

在内存中创建模板

应用程序有时会根据正在处理的数据的当前状态来调整或修改对话框的内容。 在这种情况下,在应用程序的可执行文件中提供所有可能的对话框模板作为资源是不切实际的。 但是,在内存中创建模板可让应用程序更灵活地适应任何情况。

在以下示例中,应用程序在内存中为模式对话框创建一个模板,其中包含一条消息以及 “确定” 和“ 帮助” 按钮。

在对话模板中,所有字符串(如对话框和按钮标题)都必须是 Unicode 字符串。 此示例使用 MultiByteToWideChar 函数生成这些 Unicode 字符串。

对话模板中的 DLGITEMTEMPLATE 结构必须在 DWORD 边界上对齐。 为了对齐这些结构,此示例使用一个帮助程序例程,该例程采用输入指针,并返回 在 DWORD 边界上对齐的最接近的指针。

#define ID_HELP   150
#define ID_TEXT   200

LPWORD lpwAlign(LPWORD lpIn)
{
    ULONG ul;

    ul = (ULONG)lpIn;
    ul ++;
    ul >>=1;
    ul <<=1;
    return (LPWORD)ul;
}

LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPSTR lpszMessage)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024);
    if (!hgbl)
        return -1;
 
    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
    lpdt->cdit = 3;         // Number of controls
    lpdt->x  = 10;  lpdt->y  = 10;
    lpdt->cx = 100; lpdt->cy = 100;

    lpw = (LPWORD)(lpdt + 1);
    *lpw++ = 0;             // No menu
    *lpw++ = 0;             // Predefined dialog box class (by default)

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "My Dialog", -1, lpwsz, 50);
    lpw += nchar;

    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 10; lpdit->y  = 70;
    lpdit->cx = 80; lpdit->cy = 20;
    lpdit->id = IDOK;       // OK button identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;        // Button class

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw += nchar;
    *lpw++ = 0;             // No creation data

    //-----------------------
    // Define a Help button.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 55; lpdit->y  = 10;
    lpdit->cx = 40; lpdit->cy = 20;
    lpdit->id = ID_HELP;    // Help button identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;        // Button class atom

    lpwsz = (LPWSTR)lpw;
    nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "Help", -1, lpwsz, 50);
    lpw += nchar;
    *lpw++ = 0;             // No creation data

    //-----------------------
    // Define a static text control.
    //-----------------------
    lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE)lpw;
    lpdit->x  = 10; lpdit->y  = 10;
    lpdit->cx = 40; lpdit->cy = 20;
    lpdit->id = ID_TEXT;    // Text identifier
    lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;

    lpw = (LPWORD)(lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0082;        // Static class

    for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*lpszMessage++;);
    lpw = (LPWORD)lpwsz;
    *lpw++ = 0;             // No creation data

    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, 
                           (LPDLGTEMPLATE)hgbl, 
                           hwndOwner, 
                           (DLGPROC)DialogProc); 
    GlobalFree(hgbl); 
    return ret; 
}