自身を含む複数フォームの生成と破棄

解決


take  2006-08-23 11:03:48  No: 22962  IP: 192.*.*.*

設定によって複数のフォームを切り替えるようなアプリケーションを
考えているのですが、下記のような条件を満たすような良い方法が
なかなか思いつきません。

【条件】
1.様々な種類のフォームが複数存在する
2.最初にどのフォームを表示するのかは INIファイルが決定する。
3.各フォームからINIファイルを書き換えた後は条件2で決まったフォームが
    表示される。
4.上記の条件で表示するフォームが自分自身であってもFormDestroyと
    FormCreateによって再構築を行う。

非表示フォームが各フォームを管理するSDI形式を考えてみたのですが
条件3が来るまで待機させる方法がわからず、こんな方法で組んでみました。

この方法だと各フォームの管理が1カ所で行え
フォームの切替や自身のフォームの破棄と生成もうまくいくのですが
なにせApplication.Runすら使用していない不安な内容


この方法で問題が起きないのか?またこれ以外にもっと良い方法が
無いのか教えてください。


【作成内容】
Form1とForm2、データモジュールを作成
フォームの終了や別フォーム表示の指示はデータモジュールの
プロパティを経由している(単純なクラスでもいいのかも)

各フォームのFormCreateにはDataModule1.FormStatus1や2に
自身のフォームが存在することを表す Trueを
FormDestroyには False を入れて通知

各フォームから
  DataModule1.CtrlForm := 1
  Close;
とすれば別フォームが表示され、自分自身は破棄など


プロジェクトソースに以下の処理を追加
-----------------------------------------------------------------------
program FormChangeProject;

uses
  Forms,
  Form1Unit in 'Form1Unit.pas' {Form1},
  Form2Unit in 'Form2Unit.pas' {Form2},
  DataModuleUnit in 'DataModuleUnit.pas' {DataModule1: TDataModule};

{$R *.RES}

var
  FForm2 : TForm2;
  FForm1 : TForm1;

begin
  Application.Initialize;
  Application.CreateForm(TDataModule1, DataModule1);
  DataModule1.CtrlForm = 1;
  while True do begin
    if DataModule1.CtrlForm = 2 then begin
      FForm2 := TForm2.Create(Application);
      FForm2.Show;
      DataModule1.CtrlForm := 0;
    end;
    if DataModule1.CtrlForm = 1 then begin
      FForm1 := TForm1.Create(Application);
      FForm1.Show;
      DataModule1.CtrlForm := 0;
    end;
    if (not DataModule1.FormStatus1) and
       (not DataModule1.FormStatus2) then break;
    Application.HandleMessage;
  end;
  //Application.Run;
end.
-----------------------------------------------------------------------

編集 削除
要望にあうかな?  2006-08-23 12:02:25  No: 22963  IP: 192.*.*.*

自分が使っているソースの流用ですが。こういうのはどうでしょう?

1.フォーム分のスピードボタンを用意。フォームの管理はスピードボタンのTAGプロパティで識別。
2.フォーム作成時にINIファイルを読み込み対応するスピードボタンを動かす。(DownプロパティやClickイベント?)
3.呼び出したフォームの親をメインフォーム(Form1)に設置しているPanel1に設定

uses
  Unit2, Unit3, Unit4;

procedure TForm1.SpdBtnModeUSCDClick(Sender: TObject);
var
  NewFormClass: TFormClass;
  NewForm: TForm;

begin

  //フォームを指定
  case (Sender as TSpeedButton).Tag of
    0: NewFormClass := TForm2;
    1: NewFormClass := TForm3;
    2: NewFormClass := TForm4;

  else
    NewFormClass := nil;
  end;
             
  //表示しているフォームの破棄
  if Panel1.ControlCount > 0 then
    Panel1.Controls[0].Free;

  //フォームの設定
  if Assigned(NewFormClass) then
  begin

    NewForm := NewFormClass.Create(Self);
    NewForm.Hide;
    NewForm.BorderStyle := bsNone;
    NewForm.Parent := PnlOwner;
    NewForm.Align := alClient;
    NewForm.Show;

  end;

end;

//-----------------------------------------------------------

procedure TForm2等.FormDestroy(Sender: TObject);
begin
  //必要な廃棄処理
end;

編集 削除
ミス  2006-08-23 12:05:11  No: 22964  IP: 192.*.*.*

procedure TForm1.SpdBtnModeUSCDClick(Sender: TObject);
  ↓
procedure TForm1.SpeedButtonClick(Sender: TObject);


NewForm.Parent := PnlOwner;
  ↓
NewForm.Parent := Panel1;

もうしわけありません。
自分のソースの一部が残ってました

編集 削除
take  2006-08-23 13:44:23  No: 22965  IP: 192.*.*.*

サンプルありがとうございます。
動かしてみましたが、こちらが考えているものとは少し違うようです

サンプルでいうところのメインフォームが無い状態で
Form2からFrom2と消してForm3を呼んだり、その逆を行うイメージです。

複数フォームを管理する親フォームと子となる複数のフォームで
構成出来ないか考えていますが

「自身が生成したフォームが閉じられたかどうかの判定」

という簡単な所でつまづきました。


Formを2個作ってForm1を親、Form2を子とし下記のようなのを作成

Form2のイベントを監視すればうまくいくとおもいきや
OnDestroyは発生しない。(Override用?)
OnCloseは発生するが、この時点ではまだForm2は存在するので
生成すると不具合が発生。

下記方法だとForm2の破棄後に発生するイベントが
見つかれば解決しそうなんですけど

---------------------------------------------------------
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FForm2 : TForm2;     // 追加
    FReset: Boolean;     // True:ならForm2を破棄して生成
    procedure OnSubFormClose(Sender: TObject; var Action: TCloseAction);
    procedure OnSubFormDestroy(Sender: TObject);
  public
    { Public 宣言 }
    property Reset : Boolean read FReset write FReset;
  end;

---------------------------------------------------------
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.ShowMainForm := False;  // メインフォームを非表示にする
  FForm2 := TForm2.Create(Self);
  FForm2.OnClose := OnSubFormClose;
  FForm2.OnDestroy := OnSubFormDestroy;
  FForm2.Show;
end;
---------------------------------------------------------
procedure TForm1.OnSubFormDestroy(Sender: TObject);
begin
  if FReset then begin
    FReset := False;
    //FForm2.Free;
    FForm2 := nil;
    FForm2 := TForm2.Create(Self);
    FForm2.OnClose := OnSubFormClose;
    FForm2.OnDestroy := OnSubFormDestroy;
    FForm2.Show;
  end
  else begin
    Application.Terminate;
  end;
end;

編集 削除
ふぉむ  2006-08-23 14:08:21  No: 22966  IP: 192.*.*.*

>「自身が生成したフォームが閉じられたかどうかの判定」
ですが
>Form2のイベントを監視すればうまくいくとおもいきや
>OnDestroyは発生しない。(Override用?)
>OnCloseは発生するが、この時点ではまだForm2は存在するので
>生成すると不具合が発生。

Form2をCloseしただけではメモリに残ったままです。
(Closeした状態でShowするばそのまま表示もされる)
メインフォーム以外ではそのような仕様になっています。
そのためOnDestroyは発生しません。
メモリから削除したいのであれば、Form2のOnCloseイベントでRelease;を呼んでください。
この時「自身が生成したフォームが閉じられたかどうかの判定」は
Form2のOnDestroyでも検出できますが

Form1のprotectedに
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
を追加して
procedure TForm1.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is TForm2) then
  begin
    Form2 := nil;
  end;
end;
のような方法もあります。
このルーチンはForm2が完全に破棄された後で呼ばれます。

編集 削除
ふぉむ  2006-08-23 14:16:32  No: 22967  IP: 192.*.*.*

注意、ReleaseはForm2のReleaseを呼ばなければいけませんよ。

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Release;  // = Form2.Release;
end;

編集 削除
ふぉむ  2006-08-23 14:18:33  No: 22968  IP: 192.*.*.*

と書いてて気づいた
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;
でいいじゃん

編集 削除
take  2006-08-23 16:07:34  No: 22969  IP: 192.*.*.*

Notification、そういえばコンポーネントを作るとき
お世話になっていたメソッドでしたね。

サンプルを動かしてみましたが、フォームの再生成でエラーが出ます。
動かしたときのサンプルです。
ResetプロパティがTrueのときは、Form2が破棄された後
再度生成を行えられれば解決なのですが
どうもNotificationイベント中には、まだForm2が存在しているようで

「Form2はすでに存在します」
と名前がまだ残っているというエラーメッセージが出ます。

生成したFormのNameを毎回書き換えるような仕組みにすることで
解決はできるのですが
Form2が破棄されていれば、エラーは出ないはずなので気になります。

もちろんForm2のCloseイベントで  Action := caFree;やReleaseは
しています。

もう少しだけお知恵をお貸し下さい。

----------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.ShowMainForm := False;  
  FForm2 := TForm2.Create(Self);
  FForm2.Show;

end;

procedure TForm1.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) then begin
    if(AComponent is TForm2) then begin
      FForm2 := nil;
      if FReset then begin
        FForm2 := nil;
        FReset := False;

        FForm2 := TForm2.Create(Self);
        FForm2.Show;
      end
      else begin
        Application.Terminate;
      end;
    end;
  end;

end;

編集 削除
ふぉむ  2006-08-23 16:41:20  No: 22970  IP: 192.*.*.*

Notificationの中でForm2を再作成してはいけません。
(まだ後始末が残っているため)
参考)
Form1に

inteface
 ・・・
const
  WM_DELAYEDEVENT = WM_APP+1;
type 
  TForm1 = class(TForm)
  private
  ・・・
    procedure WmDelayedEvent(var Msg:TMessage); message WM_DELAYEDEVENT;
  ・・・
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;  private
  ・・・
  end;
  ・・・
implementation
  ・・・
procedure TForm1.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) then
  begin
    if(AComponent is TForm2) then
    begin
      FForm2 := nil;
      PostMessage(Handle, WM_DELAYEDEVENT, 1, 0);
    end;
  end;
end;

procedure TForm1.WmDelayedEvent(var Msg: TMessage);
begin
  case Msg.WParam of
  1:
    if FReset then
    begin
      FForm2 := nil;
      FReset := False;

      FForm2 := TForm2.Create(Self);
      FForm2.Show;
    end
    else
      Application.Terminate;
  end;
end;
・・・
の様に時間差攻撃を仕掛けてください。

編集 削除
take  2006-08-23 19:50:31  No: 22971  IP: 192.*.*.*

移動していて遅くなりました。

上記方法でうまくいきました。
予想よりもコーディング量は多めですが親フォームだけの事なので
問題ないですね。

この方法でやってみたいと思います。
ありがとうございました。

編集 削除