MDIの不具合対処TipDelphi 2/3/4/5(5以前) で作成した MDI アプリケーションには、私が知っている範囲で3個の問題があります。
これらの問題の内 1 は子フォームの OnClose ハンドラで閉じないように 指定することで簡単に対処できますが、2 と 3 は厄介です。 そこで、2と3に対処するコードを含んだメインフォームのコードを紹介します unit MainF; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrls; type TMainForm = class(TForm) procedure FormCreate(Sender: TObject); procedure ActiveFormChange(Sender: TObject); private FixMenuMessage: UINT; protected procedure DefaultHandler(var Message); override; public procedure FixMenu; end; var MainForm: TMainForm; implementation {$R *.DFM} // FixMenu 起動用メッセージの登録 // Screen 変数の OnActiveFormChange ハンドラの登録 procedure TMainForm.FormCreate(Sender: TObject); begin FixMenuMessage := RegisterWindowMessage(PCHAR('FixMenuMessage')); Screen.OnActiveFormChange := ActiveFormChange; end; // ActiveForm の変更を検出したら FixMenu メッセージをポスト procedure TMainForm.ActiveFormChange(Sender: TObject); begin // アプリケーション終了時もアクティブフォームの変更が // 起きるが、この時既にメインフォームは破棄されているので // メッセージは送らない。 if not Application.Terminated then PostMessage(Handle, FixMenuMessage, 0, 0); end; // アクティブフォーム変更時、アクティブフォームが子フォーム // で最大化しているなら、メニューを修復する。 procedure TMainForm.DefaultHandler(var Message); begin inherited DefaultHandler(Message); if (FixMenuMessage <> 0) and (TMessage(Message).Msg = FixMenuMessage) and (ActiveMDIChild = Screen.ActiveForm) then FixMenu; end; // メニューの修復 procedure TMainForm.FixMenu; var Size: LongInt; begin // 偽の WM_SIZE をアクティブ子フォームに送って // Windows をだます。 if (ActiveMDIChild <> Nil) and (ActiveMDIChild.WindowState = wsMaximized) then begin Size := ActiveMDIChild.ClientWidth + (Longint(ActiveMDIChild.ClientHeight) shl 16); SendMessage(ActiveMDIChild.Handle, WM_SIZE, SIZE_RESTORED, Size); SendMessage(ActiveMDIChild.Handle, WM_SIZE, SIZE_MAXIMIZED, Size); end; end; end. このメインフォームを使えば、自動的に 2 の問題は解決します。3 の問題は下記のようにメニューの変更の後にメインフォームのメソッド FixMenu を呼べばメニューが修復されます。 procedure TMainForm.NewItem1Click(Sender: TObject); var NewItem: TMenuItem; begin // ファイルメニューに「プリント」メニューを追加。 NewItem := TMenuItem.Create(File1); NewItem.Caption := 'Print'; File1.Add(NewItem); // メニューの修復 FixMenu; end; 尚、本問題のうち 2, 3 は Delphi 6 では直っています。1は依然として対処が必要です。 また、WIndows NT系列のWindows
では 1 の問題は起きません。 解説MDI の 問題 2 の原因は、メニューのマージ処理が子フォームの最大化を考慮していないことに起因しています。 子フォームが最大化すると、MDI アプリケーションのクライアントウィンドウは自動的にフレームフォームのメニューに子フォームのシステムメニューやボタン類を挿入します。一方、アクティブな子フォームが切り替わると VCL は親フォームのメインメニューと新たにアクティブになった子フォームのメインメニューをマージします。 子フォームが最大化していない時は問題はないのですが、マージ処理ではフレームフォームの全てのメニュー項目をいったん削除し作り直すので、クライアントウィンドウが挿入した子フォームのシステムメニューやボタン類も削除されてしまいます。 VCL はこの消えたメニューを偽の WM_SIZE メッセージを子フォームに送ることによって復活させようとしますが、Windows 95 では WM_MDIACTIVATE のメッセージ処理中にこの処置を行うとクローズボタンの表示に不具合が出るようです。Tip での処置は、偽の WM_SIZE メッセージを WM_MDIACTIVATE の処理の「外」で送るように改良したものです。 3 の原因は 2と同じなのですが、VCL はメニューを修復する努力を 3 の場合は 全く行わないためメニューの表示が完全におかしくなってしまいます。つまりマージ処理により子フォームのシステムメニューやボタン類が削除されたメニューがそのまま表示されてしまうのです。3 の場合は 2の様にメインフォーム側で対処するのは難しいので FixMenu メソッドを明示的に呼び出してもらうことで対処することにしました。 |