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

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

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

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

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

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

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 関数は、テンプレートを読み込み、ダイアログ ボックスを表示し、ユーザーがダイアログ ボックスを閉じるまですべてのユーザー入力を処理します。

次の例では、ユーザーがアプリケーション メニューから [ アイテムの削除 ] をクリックすると、モーダル ダイアログ ボックスが表示されます。 ダイアログ ボックスには、編集コントロール (ユーザーが項目の名前を入力) と [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 は テンプレートを読み込み、ダイアログ ボックスを作成して、必要に応じて表示します。 アプリケーションは、ユーザー入力メッセージを取得してダイアログ ボックス プロシージャにディスパッチする役割を担います。

次の例では、ユーザーがアプリケーション メニューから [移動 ] をクリックすると、モードレス ダイアログ ボックス (まだ表示されていない場合) が表示されます。 ダイアログ ボックスには、編集コントロール、チェック ボックス、および [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; 
}