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

解決


take  2006-08-23 20:03:48  No: 22962

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

【条件】
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 21:02:25  No: 22963

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

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 21:05:11  No: 22964

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

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

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


take  2006-08-23 22:44:23  No: 22965

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

サンプルでいうところのメインフォームが無い状態で
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 23:08:21  No: 22966

>「自身が生成したフォームが閉じられたかどうかの判定」
ですが
>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 23:16:32  No: 22967

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

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


ふぉむ  2006-08-23 23:18:33  No: 22968

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


take  2006-08-24 01:07:34  No: 22969

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-24 01:41:20  No: 22970

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-24 04:50:31  No: 22971

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

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

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


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加