今回はダイアログボックスを扱います。「ダイアログ」とは「対話」 という意味です。つまり、ウィンドウ内に操作できる項目をいくつか配置し、ユーザにそれらを選択させること により、対話的な操作を行えるしたものです。利点の1つとして、その時点で操作できない項目を、はじめから ウィンドウ内に出さなければ誤操作する可能性がなくなるという点があります。また、膨大な操作が存在する場合 でも、うまくまとめれば割と見やすく分かりやすくなります。
ダイアログボックスは、多くの場合、元となるウィンドウがあり、そこから呼び出されて表示されるでしょう。 例えば、メニューバーから「検索」や「置換」のような項目を選ぶと「検索ダイアログ」のようなものが表示され ます。
ダイアログボックスを表示中は、元のウィンドウが操作不可能な状態になるものを、 モーダルダイアログボックスと呼びます。これは、例えば「ファイルを開く」 のようなものが当たります。多くのダイアログボックスはこのタイプのものです。元ウィンドウが操作できてしまうと、 状態が変わってしまうことが、このタイプを使う機会を増やす一因でしょう。
一方、ダイアログボックスを表示中であっても、元のウィンドウが操作できる場合、このダイアログボックスを モードレスダイアログボックスと呼びます。これには、例えば「検索」のような ダイアログが当たります。VisualC++の「検索」ダイアログの場合、小さいダイアログを画面上に出したまま、ソース コードの編集ができるようになっています。
今回扱うのは、モーダルダイアログボックスの方です。モードレスダイアログボックスは次章 に回します。実は両者は作り方がまったく違うのです。
ではプログラミングを開始しましょう。ソースコードを書く前に、例によって、リソースを作る必要があります。
メニューバーの「リソース」から「リソース追加」を選択し、出てきたダイアログから「Dialog」を 選択します。
右側に並んでいる部品を選択して、配置する位置を選択すれば配置できます。ボタンを配置した瞬間、ウィンドウの 中央部分にあるプロパティ領域が変化します。いつものように「Caption」と「ID」だけ適当に変更しておきます。 今回はボタンを2つ作り、「Caption」には「OK」と「キャンセル」を、「ID」には「1」と「2」を入力しました。
あとは、リソース自身の名前を「IDD_DIALOG1」に変更して、保存しておきます。
リソースの準備ができたら、ソースコードを書きます。
#include <windows.h> #include <tchar.h> #include "resource.h" // 定数 #define WINDOW_WIDTH (400) // ウィンドウの幅 #define WINDOW_HEIGHT (300) // ウィンドウの高さ #define WINDOW_X ((GetSystemMetrics( SM_CXSCREEN ) - WINDOW_WIDTH ) / 2) #define WINDOW_Y ((GetSystemMetrics( SM_CYSCREEN ) - WINDOW_HEIGHT ) / 2) // グローバル変数 HINSTANCE g_hInst; // プロトタイプ宣言 HWND Create(HINSTANCE hInst); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp); // 開始位置 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd) { HWND hWnd; MSG msg; g_hInst = hInst; // インスタンスハンドルをグローバル変数に保存しておく // ウィンドウを作成する hWnd = Create( hInst ); if( hWnd == NULL ) { MessageBox( NULL, _T("ウィンドウの作成に失敗しました"), _T("エラー"), MB_OK ); return 1; } // ウィンドウを表示する ShowWindow( hWnd, SW_SHOW ); UpdateWindow( hWnd ); // メッセージループ while( 1 ) { BOOL ret = GetMessage( &msg, NULL, 0, 0 ); // メッセージを取得する if( ret == 0 || ret == -1 ) { // アプリケーションを終了させるメッセージが来ていたら、 // あるいは GetMessage() が失敗したら( -1 が返されたら )、ループを抜ける break; } else { // メッセージを処理する TranslateMessage( &msg ); DispatchMessage( &msg ); } } return 0; } // ウィンドウを作成する HWND Create(HINSTANCE hInst) { WNDCLASSEX wc; // ウィンドウクラスの情報を設定 wc.cbSize = sizeof(wc); // 構造体サイズ wc.style = CS_HREDRAW | CS_VREDRAW; // スタイル wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ wc.cbClsExtra = 0; // 拡張情報1 wc.cbWndExtra = 0; // 拡張情報2 wc.hInstance = hInst; // インスタンスハンドル wc.hIcon = (HICON)LoadImage( // アイコン NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED ); wc.hIconSm = wc.hIcon; // 子アイコン wc.hCursor = (HCURSOR)LoadImage( // マウスカーソル NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED ); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // ウィンドウ背景 wc.lpszMenuName = NULL; // メニュー名 wc.lpszClassName = _T("Default Class Name");// ウィンドウクラス名 // ウィンドウクラスを登録する if( RegisterClassEx( &wc ) == 0 ){ return NULL; } // ウィンドウを作成する return CreateWindow( wc.lpszClassName, // ウィンドウクラス名 _T("Sample Program"), // タイトルバーに表示する文字列 WS_OVERLAPPEDWINDOW, // ウィンドウの種類 WINDOW_X, // ウィンドウを表示する位置(X座標) WINDOW_Y, // ウィンドウを表示する位置(Y座標) WINDOW_WIDTH, // ウィンドウの幅 WINDOW_HEIGHT, // ウィンドウの高さ NULL, // 親ウィンドウのウィンドウハンドル NULL, // メニューハンドル hInst, // インスタンスハンドル NULL // その他の作成データ ); } // ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch( msg ) { case WM_RBUTTONUP: // マウスの右ボタンが離されたとき // モーダルダイアログボックスを表示する DialogBox( g_hInst, _T("IDD_DIALOG1"), hWnd, DlgProc ); return 0; case WM_DESTROY: // ウィンドウが破棄されるとき PostQuitMessage( 0 ); return 0; } return DefWindowProc( hWnd, msg, wp, lp ); } // ダイアログプロシージャ BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { switch( msg ){ case WM_INITDIALOG: // ダイアログボックスが作成されたとき return TRUE; case WM_COMMAND: // ダイアログボックス内の何かが選択されたとき switch( LOWORD( wp ) ){ case IDOK: // 「OK」ボタンが選択された case IDCANCEL: // 「キャンセル」ボタンが選択された // ダイアログボックスを消す EndDialog( hDlg, 0 ); break; } return TRUE; } return FALSE; // DefWindowProc()ではなく、FALSEを返すこと! } |
まずウィンドウを出します。その後、マウスの右ボタンが離されたあら、モーダルダイアログボックスを表示 します。モーダルダイアログボックスを出すには、DialogBox()を使います。
INT_PTR DialogBox(HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc);
第1引数はインスタンスハンドルです。これはWinMain()の第1引数と同じものになります。今回は、あらかじめ グローバル変数に保存しておいたものを使っています。第2引数はダイアログリソースのリソース文字列を指定します。 第3引数は元ウィンドウのハンドルです。第4引数はダイアログプロシージャのアドレスです。戻り値はEndDialog()の 第2引数に指定された値が返されます。
ダイアログプロシージャとEndDialog()という単語が登場しました。ダイアログプロシージャ は、ウィンドウプロシージャのダイアログボックス版です。ウィンドウプロシージャとは別に作成しなければ なりません。上のソース例では、DlgProc()という関数を用意しました。ダイアログボックスに対するメッセージは、 すべてダイアログプロシージャの方へ送られます。
EndDialog()は、モーダルダイアログボックスを破棄する関数です。
BOOL EndDialog(HWND hDlg, INT_PTR nResult);
第1引数はダイアログボックスのハンドルです。これはダイアログプロシージャの第1引数を指定するだけです。 第2引数は、モーダルダイアログボックスを表示させるときに使ったDialogBox()の戻り値となる値を指定します。 これを使えば、モーダルダイアログボックスで何をしたのか、呼び出し元に伝えることができます。
DialogBox()は、モーダルダイアログボックスを作成するので、EndDialog()で破棄されるまで、処理の流れが 戻ってきません(このような状態を制御を返さないといいます)。ただし、この状態のときにも、ウィンドウ プロシージャは、メッセージの処理を行うことはできます。
さて、ダイアログプロシージャの作り方ですが、これはウィンドウプロシージャと似てはいますが、違いもあります。 まず、戻り値がLRESULT型ではなく、BOOL型です。戻り値としてTRUEを返すと、メッセージは処理されたことになり、 FALSEを返すと、デフォルトの処理を行ってもらうことになります。つまり、ウィンドウプロシージャのDefWindowProc() のような関数は存在せず、単にFALSEを返すだけになります。
ダイアログプロシージャの中で処理するメッセージは、WM_INITDIALOG、 WM_COMMANDの2つです。ダイアログボックスが作成されたときに、WM_INITDIALOGメッセージ が送られます。これはウィンドウでいうところのWM_CREATEメッセージにあたるものです。WM_COMMANDメッセージは、 ダイアログボックス内のボタンが選択されたとき等に送られてきます。実際に何が選択されたのかは、WPARAMの 下位ワードを調べれば分かります。
モーダルダイアログボックスが表示されているときに、元ウィンドウの方をクリックするとどうなるか確認して みて下さい。ダイアログの側が点滅して、「こちらを先に操作しろ!」って言っているのが分かると思います。 しかし、Windowsの下端(設定によって場所は違うかも知れないが)にあるタスクバーをクリックすれば、他の アプリケーションに切り替えることは可能になっています。ちなみに、他のアプリケーションへの切り替えまで不許可 になるタイプを、システムモーダルダイアログボックスと呼びます。これはあまり 使われないと思います。一般的に、他のアプリケーションの動作にまで影響を与えるようなプログラムを作らない 方がいいとされています(そういうプログラムを行儀が悪いと表現することがあります)。