使用對話方塊

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

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

顯示訊息方塊

最簡單形式的強制回應對話方塊是訊息方塊。 大部分的應用程式都會使用訊息方塊來警告使用者發生錯誤,並提示在發生錯誤之後如何繼續的指示。 您可以使用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。

範例第一個部分中的 語句會建立無強制回應對話方塊。 這些語句會在應用程式主視窗的視窗程式中,在視窗程式收到 具有IDM_GOTO功能表識別碼的WM_COMMAND 訊息時,建立對話方塊,但只有當全域變數尚未包含有效的控制碼時。 範例的第二個部分是應用程式的主要訊息迴圈。 迴圈包含 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; 
}