戻る ホーム 上へ 進む

3.2 Application 変数

Delphi で作成したアプリケーションには TApplication 型の唯一のオブジェクト Application 変数が必ずあります。Application 変数の提供する主な機能は以下の通りです

  1. アプリケーションウィンドウ
  2. メッセージループ
  3. ヒントウィンドウ、ドラッグ & ドロップ、VCL 内部メッセージの処理
  4. アプリケーションレベルのメッセージの処理

Delphi のアプリケーションの構造は普通のアプリケーションとちょっと違います。この構造の違いがアプリケーションのメッセージ処理に深く関係しています。本節では Delphi のアプリケーションの全体を統括している Application 変数について説明します

3.2.1 アプリケーションウィンドウ

3.2.1.1 アプリケーションウィンドウの役割

まず最初に下図を見てください。これは Delphi で作成したアプリケーションのメインウィンドウのシステムメニューと、タスクバー上のウィンドウボタンのシステムメニューです。

Figure 3.2-1

図3.2-1 アプリケーションのシステムメニュー

Figure 3.2-2

図3.2-2 アプリケーションのタスクバー上のシステムメニュー

普通のアプリケーションならこの二つは同じはずです。例えば、Excel を立ち上げて、タスクバー上のボタンを右クリックし、「移動」を選んで見てください。カーソルの形が変わって Excel のメインウィンドウをつかんで「移動」できるはずです。

ところが Delphi のアプリケーションには「移動」、「サイズ変更」、や「最大化」のメニューがありません。これは Delphi のアプリケーションが「メインウィンドウ」とは別に、「アプリケーションウィンドウ」を持っているからです。

Delphi のアプリケーションは、実は画面の中央に大きさが (0, 0) の「Visible」なウィンドウを持っています。「Visible」なのですが大きさが無いので結果的に見えません。これを「アプリケーションウィンドウ」と呼ぶことにします。Delphi のアプリケーションの全てのフォームはこのアプリケーションウィンドウに「所有」されたウィンドウ、つまりアプリケーションウィンドウの Owned Window になっています(下図参照 Owned Window の意味がもし判らなければ、 http://www.microsift.com の Index Server で 「Win32 Window Hierarchy and Styles」という Technical Article を探して読んでください )。

Figure 3.2-3

図3.2-3 アプリケーションウィンドウとフォームの関係

このような、構造になっているのは、Delphi が Delphi の IDE のようなアプリケーションを作ることを想定して作られているからです。Delphi のアプリケーションでは、各フォームはほとんどの場合「対等」です(MDIアプリケーションは別です)。どのフォームも Z-Order を他のフォームよりも前に持ってくることが出来ます。メインフォームさえ他のフォームの後ろに隠れてしまいます。にもかかわらず、タスクバーにフォームが姿を現さないのは、フォームがアプリケーションウィンドウの Owned Window になっているからです。

また、メインフォームの最小化ボタンを押すと、全てのフォームが瞬時に見えなくなるのは、実際には最小化されるのがメインフォームではなく、アプリケーションウィンドウだからです。Owner Window は最小化する時「所有」する Owned Windows を「隠す」性質があるのでこれが可能になっています。

もう既にお判りでしょうが、タスクバー上のボタンのシステムメニューに「移動」や「サイズ変更」が無いのは、アプリケーションウィンドウを「移動」「サイズ変更」しても無意味だからです。普通のアプリケーションでは「Primary Window」の占める位置に見えないアプリケーションウィンドウが有るのが Delphi で作成したアプリケーションの特徴です。

3.2.1.2 アプリケーションウィンドウの作成

Delphi で作成したアプリケーションは起動するとすぐに Application 変数を作成します。Application 変数はそのコンストラクタ Create でアプリケーションウィンドウをすぐに作成します。TApplication は TComponent からの派生クラスで、本来は非ウィンドウコントロールですが、にもかかわらずウィンドウを作成する珍しいコンポーネントの一つです。

TApplication.Create はまず、'TApplication' という名のクラスを作りウィンドウプロシージャに DefWindowProc をセットし、リソースからリソース名 'MAINICON' アイコンをロードしてクラスアイコンとしてセットしてクラスを登録します。

次に、メソッドポインター Application.WndProc で Object Instance を作成しウィンドウタイトルにモジュール名からディレクトリと拡張子を取ったものをセットします。表示位置を画面の中央、大きさを (0, 0) 、ウィンドウスタイルを WS_POPUP or WS_CAPTION or WS_VISIBLE or WS_CLIPSIBLINGS or WS_SYSMENU or WS_MINIMIZEBOX を指定して、ウィンドウを作成してから、上記の Object Instance でウィンドウをサブクラス化し、また、システムメニューからアプリケーションウィンドウには不要なメニュー項目を削除します。

アプリケーションウィンドウのメッセージプロシージャの Application.WndProc はウィンドウコントロールの WndProc と異なり、TObject の Diaptch メソッドを使用せず、メッセージ番号を Case 文で分岐して処理する伝統的なメッセージ処理の手法を使います。従って、Application 変数はメッセージ処理メソッドを使いません。Application.WndProc はウィンドウコントロールとは異なり、自分で直接 Windows のメッセージのデフォルト処理 DefWindowProc を呼び出します。

Application 変数が初期化されアプリケーションウィンドウが作成されると、Delphi のプログラムは、プロジェクトのメイン処理へ進みます。IDE でアプリケーションのタイトルを指定している場合、IDE はプロジェクトファイルにアプリケーションタイトルを変更する行を挿入します。

begin
  Application.Initialize;
  Application.Title := 'Snap Screen';         {IDE が挿入}
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

Application は Title Property が変更されるとアプリケーションウィンドウのウィンドウタイトルを変更します。アプリケーションが起動する時、タスクバーのウィンドウボタンのタイトルが一瞬ばたつくのはこのためです。

メイン処理はさらに、TApplication の CreateForm メソッドを呼び出して「自動作成の対象」のフォームを作成します。CreateForm メソッドは既にメインフォームが作成されているかをチェックしそうでなければ作成したフォームをメインウィンドウとしてセットします。つまり、CreateForm で最初に作成されたフォームがメインフォームになります。

メイン処理はフォームの作成が終わるとメッセージループに入ります。

3.2.2 メッセージループ

Application 変数はアプリケーションウィンドウを提供すると同時に、Windows のアプリケーションで「お約束」のメッセージループも提供します。メッセージループは TApplication.Run の中にあり、プロジェクトのメインプログラムから呼び出されます。TApplication.Run はメインフォームが作成済みであることを確認すると、メッセージループに入ります。メッセージループは TApplication.HandleMessage を呼び出し、HandleMessage は TApplication.ProcessMessage と TApplication.Idle を呼び出します(下図参照)。

Figure 3.2-4

図3.2-4 メッセージループ

ProcessMessage はまず WM_QUIT をチェックし、そうならアプリケーションを終了させます。そうでなければまず、Application 変数に設定された OnMessage ハンドラを呼び出し、次に一連のメッセージ処理関数を呼び出し、複雑な処理を行います。途中のいずれかのメッセージ処理関数でメッセージが処理済みになれば、そこで処理が中止され、 TranslateMessage/DispatchMessage に制御がわたらないことに注意してください。一連の処理のうち、MDI アクセラレータ処理(IsMDIMsg メソッド)は「お約束」の TranslateMDISysAccel を、ダイアログ関連メッセージの処理(IsDlgMsg)は Windows の本来のダイアログボックス(フォームではない)を利用する時の「お約束」の IsDialogMessage を呼び出す処理です。残りの処理は 3.3 以降で詳細に説明します。

ProcessMessage が False を返してきた場合、つまり PeekMessage がメッセージを見つけられなかった場合、HandleMessage は Idle を呼び出します。Idle はヒントや Drag & Drop の処理を行った後、Application 変数に設定された OnIdle ハンドラを呼び出します。このハンドラを使ってアプリケーションは、メッセージが来ない間バックグラウンドで行う処理を指定できます。

OnIdle ハンドラに渡される、参照渡しのパラメータ Done は重要な意味を持ちます。Done は True に設定されてハンドラに渡されますが、ハンドラが Done を False に書き換えると、Idle はすぐに HandleMessage に制御を戻すため、メッセージループが回り、すぐに次の HandleMessage が呼び出され、メッセージが無ければすぐに Idle が OnIdle ハンドラが再び呼び出します。ところが、Done を True(デフォルト)に設定されると、Idle は WaitMessage を呼び出しアプリケーションのキューにメッセージが溜まっていなければストップしてメッセージが来るのを待ちます。つまり、Done = True の場合は、PeekMessage の後に WaitMessage が呼ばれるため、実質的に GetMessage を使うのと同じ事になります。OnIdle ハンドラが設定されていない場合、Done は常に True になりますから、WaitMessage は必ず呼び出されることになります。これはメッセージループが CPU タイムを無用に消費することを防ぎます。

3.2.3 アプリケーションウィンドウのメッセージ処理

ここではアプリケーションウィンドウが行っている幾つかの重要なメッセージ処理について説明します。

まず、挙げておきたいのが WM_CLOSE をアプリケーションウィンドウが受け取った場合(例えばタスクバーのウィンドウボタンのシステムメニューで「閉じる」を選んだ場合)の処理です。アプリケーションウィンドウはメインフォームに WM_CLOSE を渡します。これは、アプリケーションウィンドウが有ることの不自然さを減らすための処理です。

次に、これはフォーム側のメッセージ処理ですが、フォームが WM_SYSCOMAND で コマンドタイプ(WPARAM の上位 12 ビット) が SC_MINIMIZE になっているメッセージを受け取ると、自分がメインフォームならば、Application.Minimize メソッドを呼び出し、自分自身は最小化しません。これは前に書いたとおり、Delphi 流のアプリケーションの動作を実現するためのものです。もちろんアプリケーションウィンドウが同じメッセージを受け取った場合(つまりタスクバーのウィンドウボタンのシステムメニューの「最小化」が選択された場合)、アプリケーションウィンドウはやはり Minimize メソッドを呼び出して自分を最小化します。

最後に、WM_ENABLE と WM_ACTIVATEAPP メッセージの処理を説明します。WM_ENABLE は例えば、モーダルなフォームを表示するためにフォームの親ウィンドウをディスエーブルする時などに発生します。また、WM_ACTIVATEAPP はアプリケーションのトップレベルウィンドウのどれかが最初にアクティブになった時、またはアプリケーションの全てのトップレベルウィンドウがアクティブでなくなった時にアプリケーションウィンドウに渡されます。この時、アプリケーションウィンドウはアプリケーションの Top Most なウィンドウを制御します。

アプリケーションウィンドウが Disable された場合、またはアプリケーションがアクティブなトップレベルウィンドウを持たなくなった場合、アプリケーションウィンドウは NormalizeTopMosts メソッドを使ってメインウィンドウを除く全ての Top Most なウィンドウを記録し、Top Most なウィンドウをノーマルなウィンドウに変えます。逆に、アプリケーションウィンドウが Enable されるか、アプリケーションのトップレベルウィンドウのどれかがアクティブになった場合、RestoreTopMosts メソッドを使って、記録しておいた Top Most なウィンドウを Top Most な状態に復帰させます。

要するに、アプリケーションウィンドウは、自分が Disable になったか、アプリケーションにアクティブウィンドウが無い場合、Top Most なウィンドウをノーマルなウィンドウに変更し、Top Most なウィンドウが、ダイアログフォームや他のアプリケーションの邪魔にならないように Z-Order を下げます。

但し、メインフォームだけは対象外になっているのに注意してください。メインフォームの FormStyle Property を fsStayOnTop に設定し、ダイアログフォームを表示しようとすると、ダイアログがメインフォームの裏に表示されてしまいます。メインウィンドウだけが除外されているのは、察するに、メインウィンドウだけが表示され、メインウィンドウを常に全面に表示して置きたいような特殊なアプリケーションを想定しているように思えます。通常はメインウィンドウを fsStayOnTop に設定するとろくなことがないので、設定しない方が無難です。

Application 変数は同様の処理を Minimize(最小化) メソッドと Restore(元の大きさに復旧) メソッドでも行います。つまり Minimize では NormalizeTopMosts を呼び、Restore では RestoreTopMosts を呼びます。

NormalizeTopMosts/RestoreTopMost は公開メソッドなので、アプリケーションの処理の中からも呼び出すことが出来ます。NormalizeTopMosts/RestoreTopMost はカウンタを持っており NormalizeTopMosts を呼ぶとインクリメントされ、RestoreTopMost がデクリメントします。NormalizeTopMosts は呼ばれる直前のカウンタ値が 0 の時、RestoreTopMost、1の時しか動作しないので、ネストして呼ぶことができます。従って、上記の WM_ENABLE, WM_ACTIVATEAPPメッセージの処理や Minimize/Restore 処理で、これらの処理が交錯しても、Top Most なウィンドウのノーマル化と復旧は正常に働きます。

以上の他にも、アプリケーションウィンドウはさまざまメッセージを処理しています。特に VCL の内部だけで使っている特殊なメッセージの処理が Delphi のアプリケーションのメッセージ処理の中で重要な役割を果たしています。これらの処理は、複数のオブジェクトが絡んだ処理なので、メッセージの種類をキーにして 3.3 以降で詳細に説明します。

3.2.4 アプリケーションウィンドウの問題点

TApplication は何点か不具合があります。

まず最初の問題点は TApplication がアプリケーションの初期のウィンドウの状態(最小化、最大化、ノーマル)に全く無関心なことです。

Delphi で作成したアプリケーションは、Delphi 1.0J の場合は WinMain の nCmdShow パラメータから、Delphi 2.0J では、CreateProcess の STARTUPINFO から、「Primary Window」の初期状態を取得し、グローバル変数 CmdShow に代入します。にもかかわらず、TApplication は全くこれを無視します。プログラムマネージャーやショートカットのプロパティで初期状態を指定しても効果は無く、アプリケーションウィンドウはノーマルな状態で生成されます。従って、CmdShow を反映させるには、アプリケーション側で処理を追加する必要があります。

もう一つは、メインフォームの表示状態(最大化、最小化、ノーマル)とアプリケーションウィンドウ(ノーマル、最小化)の表示状態の不一致を TApplication が気にしないことです。

例えば、メインフォームの WindowState property に wsMinimized を代入すると、メインフォームは最小化しますが、アプリケーションウィンドウは最小化しないため、Windows 95 ではメインフォームはタスクバーに入らずに画面の左下に小さく表示されてしまいます。この動きは、メインフォームのシステムメニューで最小化を選んだ場合や、最小化ボタンを押した時と動作が異なっています。

従って、メインフォームでは プログラムからフォームを最小化する時、WindowState を使わずに、Application.Minimize を呼ばないと多くの場合うまくありません。また、フォームを ShowWindow API 等 で最小化された場合、フォームは WM_SIZE メッセージにちゃんと応答し WinsowState Property を更新するようになっています。しかし、メインフォームはそれを、アプリケーションウィンドウには通知しません。従って、ShowWindow API を最小化に利用するには、WMSize を override して、コードを追加する必要があります。

この問題点は、「最小化」が場合によって異なる意味を持つという点で、Delphi を無用に判りにくくしていると思います。改善を望みます。

 

戻る ホーム 上へ 進む

inserted by FC2 system