やっとGUIプログラム醍醐味であるダイアログボックスの説明ができるようになった。 このチュートリアル(と次のチュートリアル)で、ダイアログボックスの使い方を習得できるようになる。
ソース1 | リソーススクリプト1 | 実行結果1 |
ソース2 | リソーススクリプト2 | 実行結果2 |
|
一つ前のチュートリアルででてきた例題で遊んでいれば、 TABキーでフォーカスを変更できないことに気づいたはずだ。 入力したいコントロールにフォーカスを変更するには、 そのコントロールをクリックしないといけないのだが、少々めんどくさい。 また、親ウィンドウの背景をいつもの白色から グレーに変更していたことに気づいたかもしれない。 これは、チャイコンと親ウィンドウのクライアントエリアとのつなぎ目をきれいにするためだ。 この問題を回避する方法もあるにはあるのだが、 簡単ではなく、全てのチャイコンをサブクラス化しなければならない。
このように不便なわけは、チャイコンはそもそも通常のウィンドウ上ではなく、 ダイアログボックス上で動作するように設計されているからだ。 ダイアログボックスのクライアントエリアは通常グレーなので、 ボタンのようなチャイコンのデフォルトカラーはグレーとなっており、 そのため、プログラマはなんの苦労もなしにそれらを融合させることができる。
詳細に入る前に、ダイアログボックスがどういうものかを説明しよう。 ダイアログボックスというのは、チャイコンと共同作業を行うように設計されたウィンドウに過ぎない。 Windowsは内部的な"ダイアログボックスマネージャ" を提供しており、 そのダイアログボックスマネージャはユーザがタブを押した時にフォーカスを移動したり、 エンターキーが押されたらデフォルトのプッシュボタンを押す、 などといったキーボード処理を行うことになっており、プログラマの手助けを担っている。 ダイアログボックスは主としてユーザからの入力を受け取ったり、 ユーザへ何らかの情報を通知するために使用される。 そのようなわけで、ダイアログボックスは入出力の「ブラックボックス」と考えるがえることができ、 つまり、ダイアログボックスを使用するために内部的にダイアログボックスがどのように処理を行っているかは知る必要がなく、 ダイアログボックスとの情報をやりとりする方法さえ知っておけばよい。 この考え方はオブジェクト指向の原則で、「隠蔽」と呼ばれている考え方だ。 もし、完全にブラックボックスとして設計されていれば、 そのブラックボックスがどのように処理を行うかについて、何の知識も無く使用することができる。 問題は、ブラックボックスが完全にブラックボックスとならなければならないことで、 実際には非常に難しいのである。 Win32APIもブラックボックスとして設計されている。
ちょっと横道にそれたので、軌道修正して本題に戻ることにする。 ダイアログボックスはプログラマの負担を軽減する働きがある。 通常、標準のウィンドウにチャイコンをのせると、サブクラス化しなければならないし、 キーボード処理も自分で書かなければならない。 しかし、ダイアログボックスに配置するときは、ダイアログボックスがプログラマの代わりにキーボード処理を行ってくれる。 なのでプログラマは、ダイアログボックスへの入力を取得する方法と、ダイアログボックスへどのようにコマンドを送信するか、 ということだけを知っておけばよいことになる。
ダイアログボックスはメニューとほとんど同じ方法でリソースとして定義する。 ダイアログボックスとそのコントロールの特徴を記述したダイアログボックステンプレートを書いて、 リソーススクリプトをコンパイルする。
全てのリソースは同じリソースファイルに書かなければならないことに注意しなければならない。 別にテキストエディタでもダイアログボックステンプレートを記述することはできるのだが、おすすめはしない。 リソースエディターを使用してビジュアルに作成したほうがよい。 ダイアログボックス上のチャイコンを手で編集するのは非常に骨の折れる作業だからだ。 いくつかの非常に優れたリソースエディタがあり、 たいていのメジャーなコンパイラ(IDE)には付属している。 それらのリソースエディタでリソーススクリプトを作成したら、 MFCに関数部分など、不必要なところは削除する。
ダイアログボックスには2つの主なタイプがある。 モーダルとモーダレスだ。 モーダレスダイアログボックスは他のウィンドウにフォーカスを移すことを許可するものだ。 MS-Word(やIE)の検索コマンドの際に出てくるダイアログボックスをイメージするとわかりやすい。 一方、モーダルダイアログボックスには2つのサブタイプがある。 アプリケーションモーダルとシステムモーダルだ。 アプリケーションモーダルダイアログボックスは、同一アプリケーションで表示されるウィンドウ間で、 フォーカスの移動ができないものだ。しかし、他のアプリケーションのウィンドウにはフォーカスが移動できる。 システムモーダルダイアログボックスはそのダイアログボックスに応答しなければ他のウィンドウに一切フォーカスを移動できない。 モーダレスダイアログボックスはCreateDialogParam関数をCALLすることによって作成できる。 対して、モーダルダイアログボックスはDialogBoxParam関数をCALLすることになっている。 アプリケーションモーダルダイアログとシステムモーダルダイアログとの唯一の違いは、 DS_SYSMODALスタイルが指定されるかどうかである。 DS_SYSMODALが指定されれば、そのダイアログボックスはシステムモーダルとなる。
SendDlgItemMessage関数を使用することにより、ダイアログボックス上のチャイコンと情報のやりとりが可能になる。 この関数の構文は以下のようになっている。
SendDlgItemMessage proto hwndDlg : DWORD,\ idControl: DWORD,\ uMsg : DWORD,\ wParam : DWORD,\ lParam : DWORDこのAPI関数はチャイコンと非常に相性が良い。例えば、エディットコントロールから文字列を取得したければ以下のようにできる。
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_bufferどのメッセージを送信すればよいかは、Win32APIリファレンスを参照せよ。
Windowsは get や set といった制御方法を指定したAPI関数も用意している。 例えば、GetDlgItemTextやCheckDlgButtonといった関数だ。 これらの関数はプログラマには非常に便利なもので、 メッセージごとに意味の異なるwParamやlParamをわざわざ調べなくてもよくなるのだ。 なので、コードのメンテナンスを容易にすることになるので、 積極的にこのような制御API関数を使用するようにしよう。 SendDlgItemMessage関数を使用するのは、そういった制御API関数が利用できないメッセージを扱うときだけにしよう。Windowsダイアログボックスマネージャは、ダイアログボックスプロシージャと呼ばれる特殊化したコールバック関数にメッセージを送信するようになっている。 そのダイアログボックスプロシージャのフォーマットは以下のようになっている。
DlgProc proto hDlg : DWORD,\ iMsg : DWORD,\ wParam: DWORD,\ lParam: DWORDダイアログボックスプロシージャはウィンドウプロシージャに非常によく似ており、 違うところは、ウィンドウプロシージャが戻り値がLRESULTだったのに対し、 ダイアログボックスプロシージャはTRUE/FALSEとなっている。 Windows内部で処理をおこなっているダイアログボックスマネージャが、 ダイアログボックスにとっての本当のウィンドウプロシージャだ。 そのダイアログボックスマネージャが、登録したダイアログボックスプロシージャをメッセージを付けてCALLするのである。
一般的な方法をおおざっぱに説明すると次のようになる。
作成するダイアログボックスプロシージャはメッセージを処理するのなら、eaxレジスタにTRUEをSETしてリターンしなければならない。 もし、そのメッセージを処理しないのなら、eaxレジスタにFALSEをSETする。 ダイアログボックスプロシージャは処理しないメッセージのときはDefWindowProc関数というようなことをしてはいけない。 というのは、ダイアログボックスプロシージャは本当のウィンドウプロシージャではないからだ。ダイアログボックスの主な2つの使い方を紹介する。 アプリケーションのメインウィンドウとして使用することもできるし、 ユーザとの情報のやりとりとして使用することもできる。 このチュートリアルでは、前者(メインウィンドウとして使用する)のプログラムを調査することにするのだが、 これにも2つの解釈の仕方がある。
- RegisterClassEx関数で登録したクラステンプレートとしてダイアログボックスを使用できる。 この場合、ダイアログボックスは単なる標準のウィンドウと同じように動作する。 つまり、ウィンドウクラスのメンバー lpfnWndProc で指定されるウィンドウプロシージャ経由でメッセージが送られてくる。 ダイアログボックスプロシージャでないのである。 この方法で便利なところは、チャイコンを作成しなくていいところで、Windowsが作成してくれるのである。 しかも、TABキーなどのキーボード処理もWindowsが担ってくれる。 さらに、ウィンドウクラス構造体にウィンドウのアイコンやカーソルを指定することもできる。
- 親ウィンドウを一切作らずに、ダイアログボックスだけを作成する。 この方法ではメッセージループを作成しなくてもよくなる。 なぜなら、メッセージが直接ダイアログボックスプロシージャに送られてくるからである。 しかも、ウィンドウクラスを登録する必要もないのである。
このチュートリアルは非常に長いものとなっている。1番目の方法を説明してから2番目の説明を続けることにする。
|
|
|
|
では、まずは最初の例の解説を始めよう。
この例では、どうやってダイアログテンプレートをウィンドウクラスとして登録し、 そのクラスから「ウィンドウ」をどうやって作成するかを紹介している。 チャイコンを作成する必要が無いので、単純なプログラムである。
MyDialog DIALOG 10, 10, 205, 60ここでは、「DIALOG」キーワードの前にある「MyDialog」というダイアログ名を宣言している。 続く数字はダイアログボックスの大きさなどを表す数字なのだが、順番に、X座標、Y座標、幅、高さ、となっている(単位はピクセルではない)。
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOKダイアログボックスのスタイルを宣言している。
CAPTION "Our First Dialog Box"これはダイアログボックスのタイトルバーに表示される文字列だ。
CLASS "DLGCLASS"この行は非常に重要で、この「CLASS」キーワードにより、 ダイアログボックステンプレートをウィンドウクラスとして使用できるようになる。 その「CLASS」キーワードに続く文字列が「ウィンドウクラス名」となる。
BEGIN EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13 PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13 END上のBEGIN−ENDブロックで、ダイアログボックス上のチャイコンを定義している。 一般に以下のような構文となっている。
control-type "text" ,controlID, x, y, width, height [,styles]control-type はりソースファイルのコンパイラが使用する定数なので、 それぞれのコンパイラに付属しているマニュアルを調べないといけない。
では、ソースコードへと移ろう。面白いところはウィンドウクラス構造体だ。
mov wc.cbWndExtra,DLGWINDOWEXTRA mov wc.lpszClassName,OFFSET ClassName通常、このメンバーはNULLのままなのだが、ダイアログボックステンプレートをウィンドウクラスとして登録するときは、 このメンバーをDLGWINDOWEXTRAにセットしなければならない。 このクラス名はリソーススクリプトで使用する「CLASS」キーワードの次にくる文字列と同一のものにしなければならないことに気をつけよう。 残りのメンバはいつもどおりにSETしよう。ウィンドウクラス構造体の設定が終わったら、RegisterClassEx関数で登録する。 見たことがあるでしょう?そう、通常のウィンドウクラスを登録するのと同じだ。
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL「ウィンドウクラス」を登録し終えた後は、ダイアログボックスを作成する。 この例では、CreateDialogParam関数を使用して、モーダレスダイアログボックスとして作成している。 この関数は5つの引数をとることになっているが、1つ目と2つ目だけをSETすればよく、 インスタンスハンドルとダイアログボックステンプレート名へのポインタだ。 クラス名へのポインタではないことに注意せよ。
この時点で、Windowsはダイアログボックスとチャイコンを作成する。 ここでもいつもと同じように、WM_CREATEメッセージをウィンドウプロシージャが受け取ることだろう。
invoke GetDlgItem,hDlg,IDC_EDIT invoke SetFocus,eaxダイアログボックスが作成されたら、エディットコントロールにフォーカスを移すのだが、 そのフォーカスを移動するコードをWM_CREATEセクションで記述しようとすると、 まだその時にはチャイコンが作られていないので、GetDlgItem関数をCALLしても失敗してしまう。 なので、GetDlgItem関数を呼ぶのは、ダイアログボックスとそのチャイコンが作成されてからになるので、 UpdateWindow関数をCALLした後に上の2行を記述することにする。 GetDlgItem関数はコントロールIDを引数にとり、関連したコントロールウィンドウハンドルを返す関数で、 コントロールIDは知っているのだが、ウィンドウハンドルがわからない、という場合に使用する。
invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIFプログラムがメッセージループに入り、Windowsからやってくるメッセージをディスパッチしたり、トランスレートする前に、 IsDialogMessage関数をCALLし、ダイアログボックスマネージャが作成したダイアログボックスのキーボード処理を行ってくれるように設定する。 この関数がTRUEを返せば、そのときのメッセージはダイアログボックスマネージャが対象としていたもので、 ダイアログボックスマネージャが処理したということになる。前章のチュートリアルとは違っていることに注意せよ。 ウィンドウプロシージャがエディットコントロールから文字列を取得するとき、 GetWindowText関数の代わりにGetDlgItemText関数をCALLする。 GetDlgItemText関数はウィンドウハンドルではなくコントロールIDを受け取ることになっており、 ダイアログボックスを使用する場合に非常に簡単になる。
|
では次に、メインウィンドウとして使用するダイアログボックスの2つ目のアプローチの解説を行う。 この例では、モーダルダイアログボックスアプリケーションを作成する。 メッセージループは探しても見つけられないだろう。なぜなら必要ないからである!
|
|
|
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORDこれにより、DlgProc関数のプロトタイプを宣言しているので、 このDlgProc関数へのポインタを以下のように使用することができる。
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL上のDialogBoxParam関数は5つの引数をとり、それらは
1 インスタンスハンドル
2 ダイアログボックステンプレート名
3 親ウィンドウハンドル
4 ダイアログボックスプロシージャのポインタ
5 そのダイアログボックス固有のデータ
となっている。DialogBoxParam関数はモーダルダイアログボックスを作成し、 ダイアログボックスが閉じられるまで制御を返さない。
.IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd,IDC_EDIT invoke SetFocus,eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0ダイアログボックスプロシージャはウィンドウプロシージャによく似ているが、 WM_CREATEメッセージを受け取らないことが違っており、 一番最初に受け取るメッセージはWM_INITDIALOGとなっている。 通常、このWM_INITDIALOGセクションに初期化コードを書けることになっているが、 もし何らかの処理を行うのなら、TRUEをeaxレジスタにSETして関数をリターンしなければならない。
内部的なダイアログボックスマネージャは、作成したダイアログボックスがWM_CLOSEメッセージを受け取ったとき、 デフォルトではWM_DESTROYメッセージをあなたが作成したダイアログボックスプロシージャに送ることはしない。 なので、もしユーザが閉じるボタンを押したときに、何らかの処理を行いたければ、 WM_CLOSEメッセージに対して行わなければならない。 この例では、wParamにIDM_EXITをSETしつつ、WM_COMMANDメッセージを送信している。 これによって、ユーザがメニューアイテムの「Exit」をクリックしたことと同じことになる。 EndDialog関数はIDM_EXITに応答してCALLされる。
そのほかの部分のWM_COMMANDメッセージに対する処理は同じなので、省略する。
ダイアログボックスを閉じたければ、EndDialog関数をCALLするのが唯一の方法だ。 絶対にDestroyWindow関数をCALLしてはならない。 EndDialog関数はすぐにはウィンドウを閉じず、ダイアログボックスマネージャが管理するフラグをSETするだけで、 次の実行コードへと続くのである。
次に、リソースファイルを見ていこう。 注目すべき変更は、メニュー名として文字列を使用するのではなく、IDR_MENU1という値を使用している点だ。 DialogBoxParam関数で作成したダイアログボックスにメニューを付けたい場合は、このような方法となる。 ダイアログボックステンプレート中で、 メニューリソースIDの後に続く「MENU」キーワード(IDR_MENU1 MENU)を忘れてはならない。
このチュートリアルにおける2つの例において、後者の例ではアイコンが無いことにすぐに気づいたことだろう。 しかしながら、WM_INITDIALOGセクションにおいて、WM_SECTIONメッセージを送ることにより、アイコンをSETすることができる。