ダイアログ ボックスの使用

ダイアログ ボックスを使用して、情報を表示し、ユーザーからの入力を求めます。 アプリケーションは、ダイアログ ボックスを読み込んで初期化し、ユーザー入力を処理し、ユーザーがタスクを完了したときにダイアログ ボックスを破棄します。 ダイアログ ボックスを処理するプロセスは、ダイアログ ボックスがモーダルかモードレスかによって異なります。 モーダル ダイアログ ボックスでは、アプリケーションで別のウィンドウをアクティブ化する前に、ダイアログ ボックスを閉じる必要があります。 ただし、ユーザーはさまざまなアプリケーションでウィンドウをアクティブ化できます。 モードレス ダイアログ ボックスでは、ユーザーからの即時の応答は必要ありません。 これは、コントロールを含むメイン ウィンドウに似ています。

次のセクションでは、両方の種類のダイアログ ボックスを使用する方法について説明します。

メッセージ ボックスの表示

モーダル ダイアログ ボックスの最も簡単な形式は、メッセージ ボックスです。 ほとんどのアプリケーションでは、メッセージ ボックスを使用してエラーをユーザーに警告し、エラーが発生した後に続行する方法の指示を求めます。 メッセージ ボックスを作成するには、 MessageBox または MessageBoxEx 関数を使用して、メッセージと表示するボタンの数と種類を指定します。 システムによってモーダル ダイアログ ボックスが作成され、独自のダイアログ ボックス テンプレートとプロシージャが提供されます。 ユーザーがメッセージ ボックスを閉じると、 メッセージ ボックス または MessageBoxEx は、メッセージ ボックスを閉じるユーザーが選択したボタンを識別する値を返します。

次の例では、エラー条件が発生した後にユーザーにアクションを求めるメッセージ ボックスがアプリケーションに表示されます。 メッセージ ボックスには、エラー条件とその解決方法を説明するメッセージが表示されます。 MB_YESNOスタイルは、ユーザーが続行する方法を選択できる 2 つのボタンを提供するように 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;    
}

次の図は、前のコード例からの出力を示しています。

message box

モーダル ダイアログ ボックスの作成

ダイアログ ボックス関数を使用してモーダル ダイアログ ボックスを作成します。 ダイアログ ボックス テンプレート リソースの識別子または名前と、ダイアログ ボックス プロシージャへのポインターを指定する必要があります。 DialogBox 関数は、テンプレートを読み込み、ダイアログ ボックスを表示し、ユーザーがダイアログ ボックスを閉じるまですべてのユーザー入力を処理します。

次の例では、ユーザーがアプリケーション メニューから [アイテムの削除 ] をクリックすると、モーダル ダイアログ ボックスが表示されます。 ダイアログ ボックスには、編集コントロール (ユーザーが項目の名前を入力) と [OK] ボタンと [キャンセル] ボタンが含まれています。 これらのコントロールのコントロール識別子はそれぞれID_ITEMNAME、IDOK、IDCANCEL です。

この例の最初の部分は、モーダル ダイアログ ボックスを作成するステートメントで構成されています。 これらのステートメントは、アプリケーションのメイン ウィンドウのウィンドウ プロシージャで、IDM_DELETEITEM メニュー識別子を持つ WM_COMMAND メッセージをシステムが受信したときにダイアログ ボックスを作成します。 この例の 2 番目の部分は、編集コントロールの内容を取得し、 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 識別子は 、[OK] ボタンと [キャンセル] ボタンに対応します。 プロシージャが EndDialog を呼び出した後、システムは追加のメッセージをプロシージャに送信してダイアログ ボックスを破棄し、ダイアログ ボックスの戻り値をダイアログ ボックスを作成した関数に戻します。

[モードレスの作成] ダイアログ ボックス

CreateDialog 関数を使用して、ダイアログ ボックス テンプレート リソースの識別子または名前、およびダイアログ ボックス プロシージャへのポインターを指定して、モードレス ダイアログ ボックスを作成します。 CreateDialog によって テンプレートが読み込み、ダイアログ ボックスが作成され、必要に応じて表示されます。 アプリケーションは、ユーザー入力メッセージを取得してダイアログ ボックス プロシージャにディスパッチする役割を担います。

次の例では、ユーザーがアプリケーション メニューから [Go To ] をクリックすると、モードレス ダイアログ ボックス (まだ表示されていない場合) が表示されます。 ダイアログ ボックスには、編集コントロール、チェック ボックス、 および [OK] ボタンと [キャンセル ] ボタンが含まれています。 ダイアログ ボックス テンプレートは、アプリケーションの実行可能ファイル内のリソースであり、リソース識別子DLG_GOTO。 ユーザーは編集コントロールに行番号を入力し、チェック ボックスをオンにして、行番号が現在の行に対して相対的であることを指定します。 コントロール識別子は、ID_LINE、ID_ABSREL、IDOK、IDCANCEL です。

この例の最初の部分のステートメントは、モードレス ダイアログ ボックスを作成します。 これらのステートメントは、アプリケーションのメイン ウィンドウのウィンドウ プロシージャで、IDM_GOTO メニュー識別子を持つ WM_COMMAND メッセージをウィンドウ プロシージャが受信したときにダイアログ ボックスを作成しますが、グローバル変数に有効なハンドルがまだ含まれていない場合に限ります。 この例の 2 番目の部分は、アプリケーションのメイン メッセージ ループです。 このループには IsDialogMessage 関数が含まれています。これにより、ユーザーはこのモードレス ダイアログ ボックスでダイアログ ボックスのキーボード インターフェイスを使用できるようになります。 この例の 3 番目の部分は、ダイアログ ボックス プロシージャです。 このプロシージャは、ユーザーが [OK ] ボタンをクリックしたときに、編集コントロールの内容とチェック ボックスを取得します。 ユーザーが [キャンセル ] ボタンをクリックすると、プロシージャによってダイアログ ボックスが破棄されます。

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 が呼び出されます。 これにより、アプリケーションで 2 つのダイアログ ボックスが同時に表示されないようにします。 このチェック 方法をサポートするには、ダイアログ ボックスを破棄するときに、ダイアログ プロシージャを 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 に設定されていることに注意してください。

ユーザーが [OK] ボタンをクリックすると、プロシージャはチェック ボックスの現在の状態を取得し、 fRelative 変数に割り当てます。 次に、変数を使用して、編集コントロールから行番号を取得します。 GetDlgItemInt は、エディット コントロール内のテキストを整数に変換します。 fRelative の値は、関数が数値を符号付き値と符号なし値のどちらとして解釈するかを決定します。 編集コントロールのテキストが有効な数値でない場合、 GetDlgItemIntfError 変数の値を 0 以外に設定します。 この手順では、この値をチェックして、エラー メッセージを表示するか、タスクを実行するかを決定します。 エラーが発生した場合、ダイアログ ボックス プロシージャは編集コントロールにメッセージを送信し、ユーザーが簡単に置き換えることができるようにコントロール内のテキストを選択するように指示します。 GetDlgItemInt がエラーを返さない場合、プロシージャは要求されたタスク自体を実行するか、所有者ウィンドウにメッセージを送信して、操作を実行するように指示できます。

ダイアログ ボックスの初期化

ダイアログ ボックスとその内容は、 WM_INITDIALOG メッセージの処理中に初期化します。 最も一般的なタスクは、現在のダイアログ ボックスの設定を反映するようにコントロールを初期化することです。 もう 1 つの一般的なタスクは、画面または所有者ウィンドウ内でダイアログ ボックスを中央に配置することです。 一部のダイアログ ボックスで便利なタスクは、既定の入力フォーカスを受け入れるのではなく、指定したコントロールに入力フォーカスを設定することです。

次の例では、ダイアログ ボックス プロシージャによってダイアログ ボックスが中央に表示され、 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; 
}