モーダルダイアログボックスを出すには、リソースを定義してDialogBox()を呼び出す、というのが一般的な方法のようです。しかしダイアログボックスを定義するリソースは座標系がやや特殊な上に、あらかじめリソースを定義しておくのでは実行時に動的にダイアログボックスを作成できないので不便です。そこで、今回はリソースもDialogBox()も使わないでモーダルダイアログを実現する方法を考えてみる事にします。
モーダルダイアログ
モーダルダイアログは、ダイアログが閉じるまでダイアログを呼び出したウインドウの操作が出来ない、つまり無効化されるダイアログです。という事は、メインウインドウからあるウインドウをモーダルダイアログボックスとして呼び出す処理は
- ウインドウを作成しメインウインドウを無効化。制御を得る。
- ダイアログの処理を行い、ダイアログが閉じられる(選択が行われる)まで待つ
- ダイアログを閉じてメインウインドウを有効化し制御を返す
とすれば実現できるはずです。つまり、モーダルダイアログを呼び出して選択結果を返す関数dialog()は
DWORD dialog() { /* モーダルダイアログ関数 */
HWND hwDialog;
MSG msg;
/* ダイアログボックス作成 */
hwDialog = CreateWindow("CDialog","モーダルダイアログ",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
256,96,NULL,NULL,hInst,NULL);
/* メインウインドウを無効化してモーダルに */
EnableWindow(hwMain,FALSE);
/* メッセージループ */
・
・
/* ダイアログが結果を返すまでループ */
/* メインウインドウを有効にしてモーダル解除 */
EnableWindow(hwMain,TRUE);
BringWindowToTop(hwMain);
/* ダイアログの選択結果を返す */
}
という流れになります。問題なのは、ダイアログの選択結果ですね。ダイアログボックスは、中にGUI部品を置いてユーザーが何らかの選択を行うわけですが、その選択処理を行うのはダイアログのウインドウプロシージャ−、つまり別の関数です。ダイアログ関数の方でダイアログボックスの選択結果を知るためには、ウインドウプロシージャと何らかの方法で選択結果をやり取りする必要があります。
別の関数と値のやりとりをするには、変数のポインタを相手に渡しその変数を相手に書き換えさせる方法かグローバル変数を使う方法が手軽でしょう。今回は、ダイアログ関数に選択結果を受け取る変数dwResを用意して、この変数のアドレスをダイアログボックスのウインドウプロシージャ−DialogProc()に渡す事にしました。ウインドウプロシージャ−の型は
LRESULT CALLBACK DialogProc(HWND,UINT,WPARAM,LPARAM);
というものでしたので、変数のアドレスを渡す時にはHWNDにNULLを渡し、変数のポインタはUINTに一時的にキャストして渡す事にしましょう。これは、ウインドウプロシージャ−の先頭に
static LPDWORD lpRes;
if (hwnd==NULL) {
/* 結果変数のアドレスを保存 */
lpRes=(LPDWORD)iMsg;
*lpRes=0;
return 0;
}
という処理を入れておけば良いでしょう。これで、DialogProc()内で*lpResの値を書き換えればその変更はダイアログ関数dialog()のローカル変数dwResに反映されます。
今回、ダイアログボックスには「1番」「2番」という2つのボタンを置いて、「1番」がクリックされたら1、「2番」なら2、どちらもクリックしないでダイアログが閉じられたらー1を返すようにしました。この場合、ダイアログボックスのウインドウプロシージャ−は以下のようになります。
LRESULT CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam) {
HWND btN1,btN2;
static LPDWORD lpRes;
if (hwnd==NULL) {
/* 結果変数のアドレスを保存 */
lpRes=(LPDWORD)iMsg;
*lpRes=0;
return 0;
}
switch (iMsg) {
case WM_CREATE: /* ウインドウ作成時の処理 */
/* ボタン作成 */
btN1=CreateWindow("Button","1番",WS_CHILD|WS_VISIBLE,
8,8,80,32,hwnd,(HMENU)0,hInst,NULL);
btN2=CreateWindow("Button","2番",WS_CHILD|WS_VISIBLE,
96,8,80,32,hwnd,(HMENU)1,hInst,NULL);
return 0;
case WM_COMMAND: /* コマンドメッセージ */
switch (LOWORD(wParam)) {
case 0: /* 1番ボタン */
*lpRes=1;
DestroyWindow(hwnd);
break;
case 1: /* 2番ボタン */
*lpRes=2;
DestroyWindow(hwnd);
break;
}
return 0;
case WM_DESTROY : /* 終了処理 */
if (*lpRes==0) /* どちらも選択されていなければ-1 */
*lpRes=-1;
return 0;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
ダイアログプロシージャ−は、選択がされるかダイアログが閉じられるとダイアログ関数のローカル変数dwResのアドレス(lpRes)が指す場所に値を書き込むので、変数dwResが「0でない間」ダイアログの処理を繰り返す事になります。
DWORD dialog() { /* モーダルダイアログ関数 */
HWND hwDialog;
DWORD dwRes;
MSG msg;
/* ダイアログのウインドウプロシージャ−に結果変数のアドレスを渡す */
DialogProc(NULL,(UINT)(&dwRes),0,0);
/* ダイアログボックス作成 */
hwDialog = CreateWindow("CDialog","モーダルダイアログ",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
256,96,NULL,NULL,hInst,NULL);
/* メインウインドウを無効化してモーダルに */
EnableWindow(hwMain,FALSE);
/* メッセージループ */
while (dwRes==0) { /* ダイアログが結果を返すまでループ */
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* メインウインドウを有効にしてモーダル解除 */
EnableWindow(hwMain,TRUE);
BringWindowToTop(hwMain);
/* ダイアログの選択結果を返す */
return dwRes;
}
ダイアログボックス関数は、以上にような感じです。この関数は、呼び出せれるとダイアログが閉じられるまで制御を得て、ダイアログが閉じられると選択結果を返します。
プログラム
今回のプログラムは、「ダイアログ」というボタンを配置したメインウインドウを作ります。ボタンをクリックすると、dialog()を呼び出してモーダルダイアログを生成し、選択結果をタイトルバーに表示します。
モーダルダイアログボックスを呼び出す処理
case 0: /* 「ダイアログ」ボタン */
dwMRes=dialog(); /* ダイアログ呼び出し */
wsprintf(lpszStr,"%dを選択",dwMRes);
SetWindowText(hwnd,lpszStr);
break;