はじめに
当然のことながら、WindowsPCは個人ごとに異なる環境で動作しています。
そのため、Windowsでゲームを起動する場合は個々の環境に対応するべく、最初に動作環境を設定する必要があるのではないでしょうか
(実を言いますと、普段あまりゲームをやらないため詳しくは知りません)。
そこで、今回はアプリのウィンドウを生成する前に、動作環境の設定を行うダイアログ表示を行いたいと思います。
ダイアログボックス
ダイアログボックスには、モーダルとモードレスの2種類が存在します。
モーダルは、ユーザーがダイアログに応答しないうちは、親ウィンドウへフォーカスを移すことができません。
一方のモードレスは、ユーザーの応答を待たずに親ウィンドウは処理を続行することができます。
いずれのダイアログボックスを生成するにしろ、まずはダイアログの定義を行う必要があります。
定義の方法はメニュー付きウィンドウと同様に、リソースファイルを使用します。
リソースファイルに、ダイアログのサイズやスタイル、表示内容としてプッシュボタンやラジオボタン等のコントロールをどのように配置するか等を記述します。
example001.rc
IDD_MYEXAMPLE_STARTUP DIALOG DISCARDABLE 100, 100, 300, 336
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION
CAPTION "Example001起動"
FONT 9, "MS Pゴシック"
{
...(中略)
PUSHBUTTON "開始", IDC_BUTTON_START, 184, 172, 97, 30
PUSHBUTTON "環境設定", IDC_BUTTON_SETENV, 184, 210, 97, 30
PUSHBUTTON "終了", IDC_BUTTON_QUIT, 184, 284, 97, 30
...(中略)
}
シンプルな例として、ダイアログボックスと3つのプッシュボタンを定義しています。
IDD_MYEXAMPLE_STARTUPはダイアログ識別名で、ヘッダファイルで値を定義しています。コード中にてダイアログを生成する際にも使用します。
次のDIALOGコマンドは、ダイアログボックスの定義を行うためのものです(※DIALOGは古く、一部の拡張スタイルに対応していないことがあります。DIALOGEXであれば対応しているため、こちらを使用すべきかもしれません)。
DISCARDABLEは、ダイアログを使用しないときはメモリから取り除くための指定です。
後ろの4つの数値は表示位置(X,Y)とダイアログの幅/高さです。
STYLEで生成するダイアログのスタイルを指定します。
ダイアログのスタイルで指定しているものの中にWS_POPUP
やWS_CAPTION
等、ウィンドウのスタイルで使用しているものと同じものがあります。
ダイアログもウィンドウの一種であることが分かります。
メインウィンドウにメッセージ処理を行うためのウィンドウ関数があるように、ダイアログにもメッセージ処理を行うためのダイアログ関数を用意する必要があります。
ダイアログ用のメッセージはウィンドウ関数には送信されません。
CExample.cpp - StartupDialogFunc()
////////////////////////////////////////////////////////////////
// 起動時ダイアログ
BOOL CALLBACK CMyExample::StartupDialogFunc(HWND hwndDialog, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
// ダイアログのボタン押下
case WM_COMMAND:
switch(LOWORD(wParam))
{
// 開始
case IDC_BUTTON_START:
EndDialog(hwndDialog, 0);
return TRUE;
// 環境設定
case IDC_BUTTON_SETENV:
DialogBoxW(hExampleInst, MAKEINTRESOURCEW(IDD_MYEXAMPLE_SETENV), hwndDialog, (DLGPROC)CMyExample::SetEnvDialogFunc);
return TRUE;
// アプリ終了
case IDC_BUTTON_QUIT:
EndDialog(hwndDialog, 0);
nMainTask = MYMAINTASK::TASK_QUIT;
return TRUE;
}
break;
}
return FALSE;
}
ダイアログ関数の引数はウィンドウ関数と同じですが、戻り値が異なります。
受信したメッセージを処理した場合はTRUE、処理しない場合はFALSEを返します。
ダイアログの各コントロールに対してユーザーが操作を行うと、WM_COMMANDメッセージが送られてきます。
上記サンプルのコードは、リソースファイルで定義したプッシュボタンのIDをswitch ~ case
で処理しています。
リソースでダイアログを定義し、ダイアログ関数を用意したら、あとは必要な場面でダイアログを起動するだけです。
DialogBoxW(hExampleInst, MAKEINTRESOURCEW(IDD_MYEXAMPLE_STARTUP), NULL, (DLGPROC)CMyExample::StartupDialogFunc);
第一引数には、WinMain関数のHINSTANCE hThisInst
を指定します。
第二引数には、起動したいダイアログの識別名を指定します。
第三引数には、ダイアログを起動する親ウィンドウへのハンドルを指定します。
第四引数には、ダイアログ関数へのポインタを指定します。
ダイアログを終了する場合はEndDialog関数を呼びます。
EndDialog(hwndDialog, 0);
第一引数には、終了したいダイアログのハンドルを指定します。
第二引数には、DialogBoxW関数が返すステータスコードを指定します。本サンプルでは戻り値を利用しないため0
の固定値を設定しています。
モーダルダイアログ
サンプルでは、動作環境の設定を完了するまで処理を先に進めたくないため、モーダルダイアログを使用しています。
DialogBoxW(hExampleInst, MAKEINTRESOURCEW(IDD_MYEXAMPLE_SETENV), hwndDialog, (DLGPROC)CMyExample::SetEnvDialogFunc);
こちらのダイアログは、最初に例示したダイアログを親ウィンドウとして指定しています。
モーダルのため、子ダイアログが終了しないうちは親ダイアログに戻ることはできません。
動作環境設定用のダイアログ関数では、ダイアログ起動時に初期値をセットしています。
CExample.cpp - SetEnvDialogFunc()
case WM_INITDIALOG:
// 初期値設定(コンボボックス)
SendDlgItemMessageW(hwndDialog, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)L" 800 * 600 24bpp");
SendDlgItemMessageW(hwndDialog, IDC_COMBO1, CB_SETCURSEL, 0, 0);
// 初期値設定(ラジオボタン)
SendDlgItemMessageW(hwndDialog, IDC_RADIO_SCREEN1, BM_SETCHECK, 1, 0);
SendDlgItemMessageW(hwndDialog, IDC_RADIO_BGM_OFF, BM_SETCHECK, 1, 0);
SendDlgItemMessageW(hwndDialog, IDC_RADIO_SE_OFF, BM_SETCHECK, 1, 0);
return TRUE;
// ダイアログのボタン押下
case WM_COMMAND:
switch(LOWORD(wParam))
{
// 設定適用
case IDC_BUTTON_APPLY:
// 画面モード
if(SendDlgItemMessageW(hwndDialog, IDC_RADIO_SCREEN1, BM_GETCHECK, 0, 0) == BST_CHECKED)
{
myenv.bFullScreen = FALSE;
}
WM_INITDIALOGメッセージがダイアログ起動時に受け取るメッセージです。
SendDlgItemMessageW
を呼び出し、各コントロールにデフォルト値を設定しています。
ボタン押下されたイベントも同様に、SendDlgItemMessageW
関数からボタンの選択状態をチェックしています。
一例として、サンプルではラジオボタンのチェック状態の設定/取得として、BM_SETCHECK
BM_GETCHECK
メッセージを使用しています。
ダイアログ用のメッセージやコントロールは他にもたくさんあり、そのすべてを解説できませんので、一例に留めておきます。
本来であれば、動作環境の設定値はファイル等で永続データとして保存し、そこから取得した値を初期値として使用する方が良いでしょう(サンプルでは、動作環境設定と称しつつも、実際には選択肢がないため保存を行ってはおりません)。
モードレスダイアログ
また、サンプルでは使用しておりませんが、モードレスダイアログについても触れておきます。
前述のとおり、モードレスではユーザーの応答を待たずに親ウィンドウの処理は進んでいきます。
つまり、その間は2つのウィンドウが存在し、各々がメッセージを受信することになります。
その点を考慮してコードを追加する必要がありますが、手間としては軽微なものでしょう。
モードレスダイアログの生成
モードレスの場合、CreateDialog関数を使用してダイアログを生成します。
HWND CreateDialog(HINSTANCE, LPCSTR, HWND, DLGPROC);
引数はDialogBox関数と同じです。戻り値のHWNDがモードレスダイアログへのハンドルとなります。
このハンドルは、ダイアログの終了時やメッセージループ等で使用するため、グローバル変数やクラスのメンバ変数として保持しておく必要があります。
生成したダイアログは非表示状態です。メインウィンドウと同じくShowWindow関数を使用するか、もしくは、リソースファイルのダイアログのスタイル指定にてWS_VISIBLEを追加してください。
モードレスダイアログの終了
モードレスの場合、DestroyWindow関数を使用してダイアログをクローズします。
BOOL DestroyWindow(HWND);
引数には生成時に取得したハンドルを指定してください。
メッセージループにダイアログ用のメッセージ受信を追加
モードレスダイアログが存在する間は、ダイアログもメインウィンドウもメッセージを受信し続けます。
そのため、メッセージループにも手を入れる必要があります。
while(GetMessage(&msg, NULL, 0, 0))
{
// ダイアログメッセージ判定
if(IsDialogMessage(hModelessDlg, &msg) == FALSE)
{
// ダイアログメッセージでなければウィンドウ関数で処理
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
IsDialogMessage関数を追加しています。
この関数は、メッセージがダイアログ向けか否かを判定します。
ダイアログメッセージであればダイアログ関数に処理をさせ、そうでなければFALSEを返します。
次回予告
今回までで、個人的に取り扱いたい題材はひと通り解説してきました。
しかし、一つ基本的なことを忘れていたことに気が付きました。
それは文字列の出力です。"Hello World"です。
次回は今更ながらに文字の出力を扱い、このシリーズを〆たいと思います。