2011年2月8日

モーダルなフォームのOnShowイベントでそのフォームを閉じる

プログラム的に必要な設定が行われていないなど、何らかの理由でShowModalされた(モーダルな)フォームのOnShowイベントで当該フォームを閉じたい、ということがたまにあります。ところがOnShowイベントで
procedure TForm2.FormShow(Sender: TObject);
begin

  Close;

end;

としてもそのフォームを閉じることはできません。これはTCustomForm.Closeの実装が
procedure TCustomForm.Close;
var
  CloseAction: TCloseAction;
begin
  if fsModal in FFormState then
    ModalResult := mrCancel
  else
    ...
(Delphi 2007のForms.pasの5589行目付近より)
とモーダルなフォームではModalResultをmrCancelに設定するだけで、一方TCustomForm.ShowModalは
function TCustomForm.ShowModal: Integer;
  ...
  try
    Show;
    try
      SendMessage(Handle, CM_ACTIVATE, 0, 0);
      ModalResult := 0;
      repeat
        Application.HandleMessage;
        if Application.Terminated then ModalResult := mrCancel else
        if ModalResult <> 0 then CloseModal;
      until ModalResult <> 0;
      Result := ModalResult;
      SendMessage(Handle, CM_DEACTIVATE, 0, 0);
      if GetActiveWindow <> Handle then ActiveWindow := 0;
    finally
      Hide;
    end;
  ...
end;
(Delphi 2007のForms.pasの5816行目付近より)
となっており、Showの内部でCloseを呼び出してModalResultをmrCancel(など0以外の値)に変更しても、Showから戻ってきてから0に戻されてしまい、"ModalResult <> 0"という終了条件を満たさないためです。

これを回避するには例えばTTimerを使う方法も考えられますが、お手軽な解決として
procedure TForm2.FormShow(Sender: TObject);
begin

  PostMessage(Handle,WM_CLOSE,0,0);

end;

と自分(モーダルなフォーム)にWM_CLOSEメッセージをpost(sendではない)するという方法があります。PostされたWM_CLOSEメッセージはメッセージキューの最後に追加され、repeat-untilループの内部のApplication.HandleMessageでディスパッチされてTCustomForm.WMCloseから最終的にCloseが呼び出されることでModalResultが0以外の値になり、終了条件を満たしてフォームが閉じる、という仕組みです。

0 件のコメント: