設定によって複数のフォームを切り替えるようなアプリケーションを
考えているのですが、下記のような条件を満たすような良い方法が
なかなか思いつきません。
【条件】
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.
-----------------------------------------------------------------------
自分が使っているソースの流用ですが。こういうのはどうでしょう?
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;
procedure TForm1.SpdBtnModeUSCDClick(Sender: TObject);
↓
procedure TForm1.SpeedButtonClick(Sender: TObject);
NewForm.Parent := PnlOwner;
↓
NewForm.Parent := Panel1;
もうしわけありません。
自分のソースの一部が残ってました
サンプルありがとうございます。
動かしてみましたが、こちらが考えているものとは少し違うようです
サンプルでいうところのメインフォームが無い状態で
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;
>「自身が生成したフォームが閉じられたかどうかの判定」
ですが
>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が完全に破棄された後で呼ばれます。
注意、ReleaseはForm2のReleaseを呼ばなければいけませんよ。
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Release; // = Form2.Release;
end;
と書いてて気づいた
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
でいいじゃん
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;
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;
・・・
の様に時間差攻撃を仕掛けてください。
移動していて遅くなりました。
上記方法でうまくいきました。
予想よりもコーディング量は多めですが親フォームだけの事なので
問題ないですね。
この方法でやってみたいと思います。
ありがとうございました。
ツイート | ![]() |