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とは扱いが異なるため、メッセージごとの仕様を確認します。
初期化時に確認する項目
- リソースIDが実際の.rcファイルに存在しているか
- hInstanceがダイアログを含むモジュールを指しているか
- lParamに渡すデータの有効期間が足りているか
- コントロールIDが重複していないか
- Unicode版とANSI版のAPIが混在していないか
メッセージループでの扱い
モードレスダイアログは、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で破棄します。
操作性を保つための処理
- IsDialogMessageでTab移動と既定キーを処理する
- 親ウィンドウが閉じる前に子ダイアログを破棄する
- WM_CLOSEとキャンセルボタンの動作を統一する
- 破棄後に保持していたHWNDをNULLへ戻す
- 再表示時に既存ウィンドウを前面へ移動する
文字コードとエラーの調査
入力欄の文字列を取得する場合、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を使う、という流れを守れば基本的なモードレスダイアログを安定して扱えます。