hozugawa.net · A neutral informational page

DialogBoxParamで作るモーダルダイアログの実装と注意点

Windows APIで設定画面や確認画面を表示する場合、ダイアログボックスは通常のトップレベルウィンドウより少ないコードで構築できます。特に DialogBoxParam は、リソースに定義したダイアログテンプレートを読み込み、モーダル形式で表示するための代表的な関数です。

モーダルダイアログを開いている間は、呼び出し元の親ウィンドウが操作できなくなります。ユーザーに入力を完了させてから次の処理へ進めたい場面や、設定値を一時的に編集させる場面で適しています。

この関数を正しく使うには、リソースID、ダイアログプロシージャ、WM_INITDIALOG、EndDialog、そして戻り値の扱いを理解することが重要です。ここではC/C++による基本的な実装と、保守時に問題になりやすい点を整理します。

関数の役割とモーダル動作

DialogBoxParam は、ダイアログテンプレートを作成して表示し、ダイアログが終了するまで呼び出し元へ制御を返しません。関数の形式は次のようになります。

INT_PTR DialogBoxParam(
    HINSTANCE hInstance,
    LPCCTSTR  lpTemplateName,
    HWND      hWndParent,
    DLGPROC   lpDialogFunc,
    LPARAM    dwInitParam
);

Unicode環境では DialogBoxParamW、マルチバイト環境では DialogBoxParamA が実体になります。DialogBoxParam はプロジェクトの文字セット設定に応じて切り替わるため、リソース名や文字列の扱いを統一するなら、明示的にW版を使う設計も有効です。

第5引数の dwInitParam は、ダイアログ生成時にアプリケーション側のデータを渡すために使います。ダイアログプロシージャでは WM_INITDIALOG の lParam として受け取れるため、設定構造体のポインターや親ウィンドウに関連するコンテキストを渡せます。

リソーステンプレートを用意する

ダイアログの外観は、通常リソーススクリプト(.rc)で定義します。Visual Studioのリソースエディターを利用する場合は、ダイアログを追加してIDを設定し、編集ボックスやボタンなどのコントロールを配置します。

IDD_OPTION DIALOGEX 0, 0, 220, 90
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "オプション"
FONT 9, "MS Pゴシック"
BEGIN
    LTEXT       "名前:", -1, 10, 12, 40, 12
    EDITTEXT    IDC_NAME, 55, 10, 145, 14, ES_AUTOHSCROLL
    DEFPUSHBUTTON "OK", IDOK, 110, 65, 45, 14
    PUSHBUTTON  "キャンセル", IDCANCEL, 160, 65, 45, 14
END

リソースヘッダーには、ダイアログとコントロールの識別子を定義します。DialogBoxParam のテンプレート名には、整数リソースIDを MAKEINTRESOURCE(IDD_OPTION) で渡すのが一般的です。文字列リソース名を使う構成では、L"IDD_OPTION" のような名前指定も可能です。

ダイアログプロシージャの基本構造

ダイアログプロシージャは、通常のウィンドウプロシージャと似ていますが、戻り値の規則が異なります。メッセージを処理した場合は TRUE、処理しなかった場合は FALSE を返します。

INT_PTR CALLBACK OptionDlgProc(
    HWND hwndDlg,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        SetDlgItemTextW(hwndDlg, IDC_NAME, L"初期値");
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            EndDialog(hwndDlg, IDOK);
            return TRUE;

        case IDCANCEL:
            EndDialog(hwndDlg, IDCANCEL);
            return TRUE;
        }
        break;
    }

    return FALSE;
}

WM_INITDIALOG は、ダイアログが表示される前に初期化処理を行うためのメッセージです。初期フォーカスを変更した場合などを除き、処理済みとして TRUE を返すのが基本です。OKボタンやキャンセルボタンでは EndDialog を呼び、モーダルループを終了させます。

呼び出し側と戻り値の扱い

親ウィンドウからダイアログを開く処理は、次のように記述できます。

INT_PTR result = DialogBoxParamW(
    hInstance,
    MAKEINTRESOURCEW(IDD_OPTION),
    hWndParent,
    OptionDlgProc,
    reinterpret_cast<LPARAM>(&optionData)
);

if (result == IDOK)
{
    // 入力値を確定する
}
else if (result == IDCANCEL)
{
    // 変更を破棄する
}

EndDialog の第2引数が DialogBoxParam の戻り値になります。したがって、単にダイアログを閉じるのではなく、IDOK、IDCANCEL、独自の終了コードなどを明確に決めておくと、呼び出し側で結果を判断しやすくなります。

失敗時の戻り値は -1 です。リソースが見つからない、インスタンスハンドルが誤っている、テンプレートが壊れているといった場合に発生します。戻り値を INT_PTR で受け、-1 をエラーとして確認すると、原因調査が容易になります。

処理対象 代表的な指定 主な意味
ダイアログテンプレート MAKEINTRESOURCE(IDD_OPTION) .rc に定義したダイアログを指定
親ウィンドウ hWndParent モーダル関係と表示位置の基準
初期データ LPARAM WM_INITDIALOG へ渡す任意の値
正常終了 IDOK 入力を確定して終了
中止終了 IDCANCEL 変更を破棄して終了
関数エラー -1 作成または読み込みに失敗

初期データと入力値を管理する

構造体のアドレスを dwInitParam で渡す場合は、ダイアログが閉じるまでその構造体が有効でなければなりません。スタック上の構造体を渡す方法は、DialogBoxParam が同期的に動作するため通常は安全です。ただし、非同期処理へポインターを保存する場合は、所有期間を別途管理する必要があります。

struct OPTION_DATA
{
    wchar_t name[64];
};

OPTION_DATA optionData{};
INT_PTR result = DialogBoxParamW(
    hInstance,
    MAKEINTRESOURCEW(IDD_OPTION),
    hWndParent,
    OptionDlgProc,
    reinterpret_cast<LPARAM>(&optionData)
);

プロシージャ側では、WM_INITDIALOG の lParam を構造体ポインターへ変換できます。複数のメッセージで同じデータを使う場合は、SetWindowLongPtr(hwndDlg, DWLP_USER, ...) で保存しておくと、各メッセージ処理から取得できます。

編集コントロールの値を読み取るときは、GetDlgItemTextW や GetDlgItemInt を利用します。文字数上限を設け、戻り値や変換結果を検証してから構造体へ格納すると、入力値によるバッファー破壊や意図しない変換を防げます。

コントロール通知を正しく処理する

WM_COMMAND では、LOWORD(wParam) からコントロールID、HIWORD(wParam) から通知コードを取得できます。ボタンのクリックだけを処理するなら、IDを確認したうえで BN_CLICKED も判定すると、意図しない通知を誤って処理せずに済みます。

case WM_COMMAND:
{
    const int controlId = LOWORD(wParam);
    const int notifyCode = HIWORD(wParam);

    if (controlId == IDOK && notifyCode == BN_CLICKED)
    {
        // 入力検証
        EndDialog(hwndDlg, IDOK);
        return TRUE;
    }
    break;
}

入力検証に失敗した場合は EndDialog を呼ばず、エラーメッセージを表示してダイアログを開いたままにします。MessageBox を使う場合は、対象ダイアログを親ウィンドウに指定すると、操作対象の関係が分かりやすくなります。

OKボタンを既定ボタンにする場合でも、Enterキーやアクセラレーターから同じ処理へ到達する可能性があります。確定処理を独立した関数にまとめ、クリック経路だけに依存しない構成にすると、入力検証の漏れを抑えられます。

モーダル処理で起きやすい問題

モーダルダイアログの表示中、呼び出し元の関数は DialogBoxParam から戻りません。そのため、ダイアログ内で設定した値を呼び出し側が直ちに確認できる一方、別スレッドから親ウィンドウを操作するような設計には注意が必要です。UI操作は原則としてUIスレッドで行います。

DialogBoxParam と CreateDialogParam を混同しないことも大切です。前者はモーダルで、終了まで処理を待機します。後者はモデルレスダイアログを作成し、呼び出し側がメッセージループや表示状態を管理します。用途に応じて選択しなければ、画面が操作できない、あるいは意図せず処理が先へ進む問題が起こります。

Windows APIの関数仕様や定数の確認には、関連するWin32技術資料も役立ちます。特に戻り値の型、Unicode版とANSI版の違い、ダイアログメッセージの順序を確認すると、古いサンプルを現行の開発環境へ移植しやすくなります。

安全で保守しやすい実装の確認項目

実装後は、表示できることだけでなく、リソース設定、終了経路、入力検証、エラー処理を確認します。次の項目をあらかじめ点検しておくと、ダイアログが閉じない問題や値が反映されない問題を見つけやすくなります。

ダイアログプロシージャでは、処理したメッセージと未処理のメッセージを明確に分けます。未処理のケースで FALSE を返す慣例を守ると、既定のダイアログ処理が働き、キーボード操作やフォーカス移動との衝突を避けられます。

実用上は、リソースIDの確認、初期化データの受け渡し、入力値の取得、終了コードの判定を一つずつ検証するのが効果的です。モーダルダイアログを追加するときは、DialogBoxParamW、WM_INITDIALOG、EndDialog、戻り値の確認を一組の基本パターンとして実装すると、安定したWindows API画面を構築できます。