使用對話框

您可以使用對話框來顯示資訊,並提示使用者輸入。 您的應用程式會在使用者完成工作時載入並初始化對話框、處理使用者輸入,以及終結對話方塊。 處理對話框的過程會根據對話框是模態還是非模態而有所不同。 強制回應對話框需要使用者先關閉對話框,再啟用應用程式中的另一個視窗。 不過,用戶可以在不同的應用程式中啟用視窗。 無模態對話框不需要使用者立即回應。 它類似於包含控制件的主視窗。

下列各節將討論如何使用這兩種類型的對話框。

顯示消息框

最簡單的模態對話框形式是訊息框。 大部分的應用程式都會使用消息框來警告使用者發生錯誤,並提示在發生錯誤之後如何繼續的指示。 您可以使用 MessageBox 或 MessageBoxEx 函式來建立消息框,並指定要顯示的訊息和按鈕數目和類型。 系統會建立模態對話框,並提供自己的對話框範本和程序。 當使用者關閉消息框之後,MessageBox 或 MessageBoxEx 會傳回值,識別使用者選擇關閉消息框的按鈕。

在下列範例中,應用程式會顯示消息框,在發生錯誤狀況之後提示使用者執行動作。 消息框會顯示描述錯誤狀況以及如何解決的訊息。 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 訊息時,建立對話方塊。 範例的第二個部分是對話框程式,它會擷取編輯控件的內容,並在收到 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; 

在此範例中,應用程式會將主視窗指定為對話框的擁有者視窗。 當系統首次顯示對話框時,其位置是相對於擁有者窗口客戶區的左上角。 應用程式會使用來自 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。

範例第一個部分中的語句會產生無模式對話框。 這些語句在應用程式主視窗的視窗程序中,當視窗程序收到 WM_COMMAND 訊息,且該訊息具有IDM_GOTO選單識別碼時,會建立對話框,但前提是全域變數尚未包含有效的控制代碼。 此範例的第二個部分是應用程式的主要訊息迴圈。 迴圈包含 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_INITDIALOG 和 WM_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 函式,才能擷取所需控件的視窗控制代碼。

在記憶體中建立範本

應用程式有時會根據所處理數據的目前狀態來調整或修改對話框的內容。 在這種情況下,提供所有可能的對話框範本作為應用程式可執行檔中的資源並不實用。 但是,在記憶體中建立範本可讓應用程式更有彈性地適應任何情況。

在下列範例中,應用程式會在記憶體中為模式對話框建立範本,其中包含訊息以及 OK 和 說明 按鈕。

在對話框範本中,所有字元字串,例如對話框和按鈕標題,都必須是 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; 
}