hozugawa.net · A neutral informational page

CreateDialogParamでモードレスダイアログを作成する

Windows APIで設定画面や検索パネルを表示する場合、メインウィンドウを操作できる状態のままダイアログを開くモードレス方式が適しています。CreateDialogParamは、ダイアログリソースを読み込み、初期化用の値をダイアログプロシージャへ渡しながらウィンドウを生成する関数です。

モーダルダイアログを作るDialogBoxParamと違い、呼び出し元の処理を停止させない点が大きな違いです。そのため、生成後はアプリケーション側でウィンドウハンドルを管理し、メッセージループでもダイアログ用のキーボード処理を考慮する必要があります。

ダイアログプロシージャにはWM_INITDIALOG、WM_COMMAND、WM_CLOSEなどが送られます。CreateDialogParamの第5引数で渡した値は、WM_INITDIALOGのlParamから受け取れるため、親ウィンドウや設定データへのポインターを初期化時に利用できます。

実装では、リソースID、インスタンスハンドル、戻り値の確認、破棄処理を一つの流れとして設計します。特に、モードレスダイアログに対してEndDialogを使わないことが重要です。

CreateDialogParamの役割と引数

関数の宣言は次のようになっています。

HWND CreateDialogParam(
    HINSTANCE hInstance,
    LPCWSTR   lpTemplateName,
    HWND      hWndParent,
    DLGPROC   lpDialogFunc,
    LPARAM    dwInitParam
);

ANSI版ではCreateDialogParamA、Unicode版ではCreateDialogParamWが使用されます。通常は、プロジェクトの文字セットに合わせたCreateDialogParamマクロを使います。第1引数にはリソースを含む実行モジュールのインスタンス、第2引数にはダイアログテンプレートのリソースID、第3引数には親ウィンドウを指定します。

第4引数はダイアログプロシージャです。第5引数のdwInitParamは任意の値で、構造体へのポインター、親ウィンドウのハンドル、初期表示モードなどを渡せます。関数が成功するとダイアログのHWNDが返り、失敗時はNULLになります。

HWND g_hSettings = nullptr;

void ShowSettingsDialog(HWND hOwner, HINSTANCE hInst)
{
    if (g_hSettings != nullptr) {
        SetForegroundWindow(g_hSettings);
        return;
    }

    g_hSettings = CreateDialogParamW(
        hInst,
        MAKEINTRESOURCEW(IDD_SETTINGS),
        hOwner,
        SettingsProc,
        reinterpret_cast<LPARAM>(hOwner)
    );

    if (g_hSettings != nullptr) {
        ShowWindow(g_hSettings, SW_SHOW);
        UpdateWindow(g_hSettings);
    }
}

リソース定義と初期化処理

ダイアログテンプレートはリソースファイルに定義します。IDD_SETTINGSには、幅や高さ、スタイル、配置するコントロールなどを記述します。モードレスとして使う場合も、通常のダイアログリソースと同じ形式で作成できます。

WM_INITDIALOGでは、コントロールへの初期値設定やフォーカスの指定を行います。lParamの値を後から使う場合は、SetWindowLongPtrでダイアログウィンドウに保存しておくと、各メッセージで参照できます。

INT_PTR CALLBACK SettingsProc(
    HWND hwndDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (message) {
    case WM_INITDIALOG:
        SetWindowLongPtrW(hwndDlg, DWLP_USER, lParam);
        SetDlgItemTextW(hwndDlg, IDC_PATH, L"C:\\Work");
        return TRUE;

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

        case IDCANCEL:
            DestroyWindow(hwndDlg);
            return TRUE;
        }
        break;
    }

    return FALSE;
}

WM_INITDIALOGでTRUEを返すと、システムが既定のフォーカス設定を行います。特定のコントロールへ明示的にフォーカスを移した場合はFALSEを返す設計もあります。なお、ダイアログプロシージャの戻り値は通常のウィンドウプロシージャのLRESULTとは扱いが異なるため、メッセージごとの仕様を確認します。

初期化時に確認する項目

メッセージループでの扱い

モードレスダイアログは、CreateDialogParamから戻った後もメッセージループを継続します。通常のGetMessage、TranslateMessage、DispatchMessageだけでも表示できますが、Tabキーや矢印キーによるダイアログ操作を機能させるにはIsDialogMessageを呼び出します。

MSG msg;

while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
    if (g_hSettings != nullptr &&
        IsDialogMessageW(g_hSettings, &msg)) {
        continue;
    }

    TranslateMessage(&msg);
    DispatchMessageW(&msg);
}

複数のモードレスダイアログを扱う場合は、すべての有効なHWNDに対してIsDialogMessageを試す方法があります。対象のウィンドウが破棄された後もグローバル変数に古いハンドルを残すと、再表示やキー処理で問題が起きるため、WM_NCDESTROYなどでNULLに戻します。

閉じる操作では、WM_CLOSEを受けてDestroyWindowを呼ぶ構成が一般的です。IDOKやIDCANCELも同じ処理へまとめられます。DialogBoxParamで作ったダイアログならEndDialogが終了手段になりますが、CreateDialogParamで作ったウィンドウはDestroyWindowで破棄します。

操作性を保つための処理

文字コードとエラーの調査

入力欄の文字列を取得する場合、GetDlgItemTextWなどのUnicode APIを基本にすると、文字化けを避けやすくなります。旧来のマルチバイト処理をUnicodeアプリケーションへ移行する場合は、WideCharToMultiByteとMultiByteToWideCharの使い分けが重要です。変換の考え方は、マルチバイトとワイド文字の変換にも関連します。

Win32 APIの文字列バッファでは、文字数とバイト数を取り違えないようにします。W版の関数は通常UTF-16のワイド文字数、A版の関数はシステムのコードページに依存するバイト列を扱います。構造体や設定ファイルへ保存する段階で文字コードを統一すると、ダイアログ側の処理も単純になります。

CreateDialogParamがNULLを返した場合は、直後にGetLastErrorを確認します。リソースが見つからない、インスタンスが誤っている、テンプレートが壊れているといった原因を切り分けられます。エラーコードを利用者向けの文言に変換する場合は、FormatMessageの表示方法が役立ちます。

ただし、すべての失敗原因が同じ形式でGetLastErrorに現れるとは限りません。デバッガでリソースID、呼び出し時のhInstance、ダイアログプロシージャのアドレスを確認し、リソースエディターでテンプレートの存在も調べます。

終了処理と方式の使い分け

モードレスダイアログでは、表示中のHWNDをアプリケーションの状態として保持します。メインウィンドウが閉じるときに残っているダイアログをDestroyWindowで閉じ、ダイアログ自身が破棄された時点でハンドルを無効化します。これにより、二重生成や破棄済みウィンドウへの操作を防げます。

用途によっては、モードレス方式よりDialogBoxParamの方が適切な場合もあります。入力を完了するまで親ウィンドウを操作させない確認画面ならモーダル方式、設定画面や検索画面を開いたまま編集を続けるならモードレス方式が向いています。

比較項目 CreateDialogParam DialogBoxParam
表示方式 モードレス モーダル
戻り値 ダイアログのHWND 終了コード
呼び出し元 メッセージループを継続 関数が終了まで待機
終了処理 DestroyWindow EndDialog
主な用途 設定、検索、ツールパネル 確認、入力、警告

実装時は、生成、表示、メッセージ処理、終了、ハンドル無効化の五つを分けて考えると見通しがよくなります。まずCreateDialogParamの戻り値を確認し、表示後はIsDialogMessageを組み込み、ボタンや閉じる操作ではDestroyWindowを使う、という流れを守れば基本的なモードレスダイアログを安定して扱えます。