Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年02月13日] [更新 2022年02月19日] Delphi サンプルプログラム集
123_フォームの再利用
動作確認等 Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32
123_ReusingForm.zip [4.4 MB] 2022年02月19日版 (EXE 同梱)







フォームの再利用


フォームも、一般的なユニットと同じように、uses 部にそのフォームのユニット名を追加して、他のプロジェクトで使用することができます。一般的なユニットと違うのは、フォームの場合、拡張子が .pas のユニットファイルと、拡張子が .dfm のフォームファイルが同時に必要であることです。drm ファイルには、フォームに配置しているコントロール類のプロパティ関係の値が書き込まれています。

アプリを開発すると、多くのフォームを作成して使用することがあります。それらのフォームには、他のアプリで流用できそうなフォームもあります。
アプリで使用するフォームの多くは、各々のアプリ特有になりますが、ShowMessage ダイアログのような、汎用的なダイアログをを自前で作成すれば、表示位置やフォントサイズも自由に設定できるようになります。



[備考]
他のプロジェクトで使用していたのフォームを再利用する場合、コードはもちろんですが、デザイン等の修正、編集も必要なこともあります。フォームの名前とユニット名も変更する必要があるでしょう。それでも新規に最初からフォームを作成するよりも手間が省けることがあります。





01_プロジェクトにユニット ( フォーム ) を追加して利用する


あるユニットをプロジェクトで使用するには、プロジェクトが、そのユニットを認識、検索できなければなりません。それには以下の方法があります。


  • pas ファイルと dfm ファイルをライブラリパスの通ったフォルダ内に置く
  • pas ファイルと dfm ファイルをプロジェクトファイルがあるフォルダ内に置く
  • [プロジェクト] [プロジェクトに追加] で、利用したいユニットをプロジェクトに追加する

最初の方法は、コンポーネントのユニット等、汎用的なユニットに対する方法です。汎用的なフォームを作成するのであればこの方法となります。

コンパイラが、必要なファイルを最初に探しにいくのは、プロジェクトファイルがある場所です。そのことを利用するのが 2 番目の方法てす。使用するフォームが少ない時にこの方式にすることがあります。この場合も、ユニットをプロジェクトに追加して使用すると便利です。プロジェクトに追加しない場合は、実行時にフォームのインスタンスを生成して使用します。

プロジェクトに追加する方法は確実で便利な方法です。追加するユニットは、既存のプロジェクト内のファイルでなく、専用のフォルダ等を作成し、そこにファイルをコピーして、それを使用するようにします。追加したフォームは、実行時に自動的に生成されますが、自動生成しないようにすることもできます。
プロジェクトに追加したユニットは、プロジェクトの他のユニットからも参照可能になり、uses 部にユニット名を追加すれば、そのユニットにアクセスできます。ユニット名の変更もプロジェクトマネージャーで可能になります。



図1
添付のプロジェクトの構成
  • Test1_Units 内に、利用するユニットがある
  • それらのユニットをプロジェクトに追加すれば、このフォルダにライブラリパスを通す必要はない
  • フォームの場合は、このフォルダに拡張子が .dfm のフームファイルも置いておく

新規に VCL フォームのプロジェクトを作成すると、Unit1.pas というフォームのユニットが作成されます。このユニットは当然このプロジェクトに追加されたことになります。これはメインフォームです。メインフォームは自動生成のフォームです。メインフォームは、Show メソッドを使用しなくても EXE 実行時に表示されます。
以下の記事で、新規に作成したプロジェクトにフォームを追加する方法を説明しています。この追加したフォームもプロジェクトに自動的に追加されます。
メインフォーム以外のフォームは、Show メソッドまたは ShowModal メソッドを使用して表示します。






02_プロジェクトに追加して利用するフォームのサンプル


テスト用に HogeForm というフォームを用意しています。このフォームには、他のフォームのユニットへの参照がありません。このフォームは、Delphi XE2 以降であればどのプロジェクトからも変更なしで使用できます。つまり、汎用的なフォームですが、内容は汎用的とは言えません。uses 部の ユニットスコープ名を削除すれば Delphi XE 以前でも使用できます。


図2
添付のテスト用のフォームの構成
  • 以下のコントロールを配置している
    TButton
    TImage
    TPanel

リスト1
テスト用のフォームのコード
unit HogeFormUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  THogeForm = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Image1: TImage;
    procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private 宣言 }
    FBackColor : TColor;
    FDispCtrl : TMemo;
    procedure SetBackColor(const Value: TColor);
  public
    { Public 宣言 }
    procedure ColorChange(AText: string);
    procedure DrawToImage(Sender: TObject; AImage: TImage; ADrawKind: Integer);
    property BackColor : TColor read FBackColor write SetBackColor;
    property DispCtrl : TMemo read FDispCtrl write FDispCtrl;
  end;

var
  HogeForm: THogeForm;

implementation

{$R *.dfm}

{ THogeForm }

//-----------------------------------------------------------------------------
//  公開メソッド
//  Panel1 の色を変更するメソッド
//-----------------------------------------------------------------------------
procedure THogeForm.ColorChange(AText: string);
var
  LColor : TColor;
begin
  LColor := RGB(Random(255), Random(255), Random(255));
  Panel1.Color := LColor;

  Panel1.ParentBackground := False;
  Panel1.Caption := AText;
end;

//-----------------------------------------------------------------------------
//  公開メソッド
//  引数の TImage の Canvas への描画用メソッド
//-----------------------------------------------------------------------------
procedure THogeForm.DrawToImage(Sender: TObject; AImage: TImage;
  ADrawKind: Integer);
var
  LCanvas : TCanvas;
begin
  if Sender.ClassName <> 'TButton' then Exit;

  if ADrawKind = 1 then begin
    AImage.Picture.Assign(nil);

    LCanvas := AImage.Canvas;
    LCanvas.Pen.Width := 5;
    LCanvas.Pen.Color := clRed;
    LCanvas.MoveTo(0, 0);
    LCanvas.LineTo(AImage.Width, AImage.Height);
  end;
end;

//=============================================================================
//  Image1 (TImage) の OnMouseMove イベント
//  プロパティ FDispCtrl (TMemo) が設定されている場合はその Text に値を代入
//  代入する値は TImage 上のマウス座標
//
//  このような情報は TApplicationEvents コンポーネントを使用すれば
//  利用側kの TApplicationEvents.OnMessage イベントでも取得可能
//=============================================================================
procedure THogeForm.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  LText : string;
begin
  if FDispCtrl <> nil then begin
    LText := Self.Name + '.' + TControl(Sender).Name + sLineBreak
           + 'X : ' + IntToStr(X) + sLineBreak + 'Y : ' + IntToStr(Y);
    FDispCtrl.Text := LText;
  end;
end;

//-----------------------------------------------------------------------------
//  プロパティ FBackColor の設定用メソッド
//  FBackColor はこのフォームの色
//  TForm.Color プロパティと同じであるか、プロパティの実装サンプルとして・・・
//
//  アプリの開発では関係するユニットを uses して使用するこも多い
//  その場合は、このようなプロパティの設定は不要だろう
//-----------------------------------------------------------------------------
procedure THogeForm.SetBackColor(const Value: TColor);
begin
  if csLoading in ComponentState then Exit;

  if FBackColor <> Value then begin
    FBackColor := Value;
    Self.Color := FBackColor;
  end;
end;

end.



[備考]
Delphi では、プロパティの変数も含めて、ユニット内のグローバル変数のプリフィクスには大文字の F が慣習的に使われています。プロパティの取得用メソッド名には Get, 設定用のメソッド名には Set というプリフィクスが慣習的に使用されています。
プロパティの取得や設定の循環実行を回避するため、ユニット内部におけるプロパティの操作には、通常は private 部で宣言した変数を使用します。





03_添付のサンプルフォームを使用するプロジェクトの例


フォルダ [03_HogeForm] に上のテスト用フォームを表示するプロジェクトがあります。このプロジェクトを IDE で開いて [F9] でコンパイルを開始すると以下のエラーが発生します。これは、HogeForm がプロジェクトに追加されていないために発生します。[プロジェクト] [プロジェクトに追加] で HogeForm を追加してください。
HogeForm は [03_HogeForm] [Test1_Units] にあります。追加するのはユニットファイル HogeFormUnit.pas です。

このエラーのパス名は、プロジェクトが最初にファイルを探しにいく場所です。拡張子 .dcu はコンパイル済みのファイルです。Delphi はコンパイル済みのファイルを探しにいきます。


[dcc32 致命的エラー] Project1.dpr(5): F1026 ファイル 'D:\Delphi\Samples\123_ReusingForm\03_HogeForm\HogeFormUnit.dcu' が見つかりません


下図は実行結果の例です。
この添付のプロジェクトは、プロジェクトにはないフォームもプロジェクトに追加すれば利用できることを確認するために作成しています。処理コードはそのこととは直接関係ありません。
動作内容はコード中のコメントで確認してください。


図3
実行結果の例
  • 左下が HogeForm を使用するメインフォーム
  • 右上は表示した HogeForm

リスト2
HogeForm を日表示する側のフォームのコード (メインフォームのコード)
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Image1: TImage;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }

  end;

var
  Form1: TForm1;

implementation

uses 
  HogeFormUnit;

{$R *.DFM}

//=============================================================================
//  フォーム表示開始時の処理
//
//  HogeForm は HogeFormUnit.pas 内で宣言されている THogeForm 型のグローバル変数
//  フォームを自動生成する時はこの名前が使用される
//
//  VCL のフォームを新規作成すると、このようなグローバル変数が宣言される
//  このフォームの場合は TForm1 型の Form1 という名前
//  この宣言は勝手に書き換えてはいけない。変更はオブジェクト・インスペクタで行う
//  このインスタンスは識別子 Self で参照できる (Form1.Caption = Self.Caption)
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  HogeForm.DispCtrl := Memo1;
end;

//=============================================================================
//  フォーム HogeForm を表示する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  HogeForm.Left := Self.Left + 200;
  HogeForm.Top  := Self.Top - 250;
  HogeForm.Show;
end;

//=============================================================================
//  フォーム HogeForm のメソッドを実行
//  HogeForm 上に配置した TPanel の色が変わる
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
 HogeForm.ColorChange(ExtractFileName(Application.ExeName));
end;

//=============================================================================
//  フォーム HogeForm の TImage に画像を描画
//  HogeForm の Image1 への描画を呼び出し側で実行する例
//  画像ファイルは ZIP 書庫に添付
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LBitmap : TBitmap;
  LImage  : TImage;
begin
  // Limage := Image1 にするとこのフォームの Image1 への描画
  LImage := HogeForm.Image1;

  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('smail_25.bmp');
    LBitmap.AlphaFormat := afDefined;
    LImage.Canvas.Draw(0, 0, LBitmap);
  finally
     FreeAndNil(LBitmap);
  end;

  LImage.Canvas.Pen.Width := 5;
  LImage.Canvas.Pen.Color := clBlack;
  LImage.Canvas.MoveTo(LImage.Width, 0);
  LImage.Canvas.LineTo(0, LImage.Height);
end;

//=============================================================================
//  フォーム HogeForm の Image1 への描画
//  HogeForm 内の描画コードを実行する例
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
begin
  HogeForm.DrawToImage(Sender, HogeForm.Image1, 1);
end;

//=============================================================================
//  フォーム HogeForm の描画コードを実行
//  HogeForm 内の描画コードをこのフォームの Image1 (TImage) に対して実行する例
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
begin
  HogeForm.DrawToImage(Sender, Image1, 1);
end;

//=============================================================================
//  フォーム HogeForm の BackColor プロパティを設定
//  このプロパティは TForm.Color プロパティと同じ
//  プロパティの実装例として別名のプロパティにしてみた
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
begin
  HogeForm.BackColor := clSilver;
end;

end.




04_プロジェクトに追加したユニットの相対パスと絶対パス


上のサンプルでは、プロジェクトに HogeFormUnit を追加しました。追加したユニットのパス名は [プロジェクト] [ソースの表示] で確認できます。上のサンプルのプロジェクトの場合は下図のようになります。これは相対パスです。
プロジェクトファイルや使用するユニット類を別の場所に移動すると、この相対関係が失われてしまいます。その結果、前項の最初の説明のように「見つかりません」のエラーが発生します。プロジェクトファイルと使用するユニットの相対位置を保ったまま移動すればこの現象を回避できます。

「見つかりません」のエラーが発生した場合は、[プロジェクト] [プロジェクトから削除] でユニットを削除して、再度プロジェクトに追加します。あるいは、プロジェクトのソースコードを直接書き換えて、[Shift] + [F9] でプロジェクトを再構築 (ビルド) します。



図4
相対パス

ユニットファイルの置き場所によっては、下図のように絶対パスとなることがあります。この場合はプロジェクトファイルを移動しても「見つかりません」のエラーは発生しません。

ここでいうプロジェクトファイルは、メインフォームのファイルを含めた意味です。



図5
絶対パス




05_フォーム ( ユニット ) 間のメッセージやイベント


あるユニットで発生したイベントやメッセージを、他のユニットでも、そのイベントやメッセージに対応した処理をしたい場合があります。そk場合、SendMessage 関数や TApplicationEvents.OnMessage イベントを使用する方法もありますが、以下のサンプルのような方法もあります。
それは、イベントやメッセージが発生したら、受信側のメソッドを実行する方法です。受信側にはあらかじめ受信用のメソッドを実装しておきます。送信側のユニットの uses 部に受信側のユニット名を追加しておきます。
フォルダ [05_Message_Event] にそのテスト用のサンプルがあります。


図6
実行画面
  • 上の HogeFormEx フォームの Button1 をクリックするとフォームの色が変わる
  • これは、Form1 の Button2 のイベントのコードを実行している
  • したがって、Form1 の Button2 をクリックしても色が変わる
  • HogeFormEx を移動すると、Form1 の Memo1 に HogeFormEx の位置をリアルタイムで表示する

リスト3
フォーム ( ユニット ) 間のメッセージやイベントのテスト用フォームのコード
unit HogeFormExUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  THogeFormEx = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure WMMove(var Message: TWMMove); message WM_MOVE;
  public
    { Public 宣言 }
  end;

var
  HogeFormEx: THogeFormEx;

implementation

uses
  Unit1;

{$R *.dfm}

{ THogeForm }

//=============================================================================
//  Button1.OnClich イベント
//  このイベントが発生したら Form1 の指定メソッドを実行する
//  Form1 では、このフォームの Button1 クリックが検出できることになる
//
//  このコードは Form1.Buttn2 のクリックイベントのコードを実行するが、
//  Form1.Button2 をクリックするのではない。したがってフォーカスの移動はない
//=============================================================================
procedure THogeFormEx.Button1Click(Sender: TObject);
begin
  Form1.Button2Click(Sender);
end;

//-----------------------------------------------------------------------------
//  このフォームの WM_MOVE メッセージ処理
//  このメッセージが発生したら From1 の指定メソッドを実行する
//  Form1 で、このフォームで発生したメッセージに対応した処理が可能になる
//-----------------------------------------------------------------------------
procedure THogeFormEx.WMMove(var Message: TWMMove);
begin
  inherited;
  Form1.ReceiveFormMove(Point(Message.XPos, Message.YPos));
end;

end.


リスト4
メインフォームのコード
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure FormShow(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure ReceiveFormMove(APos: TPoint);
  end;

var
  Form1: TForm1;

implementation

uses 
  HogeFormExUnit;

{$R *.DFM}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  Memo1.Lines.Clear;
end;

//=============================================================================
//  フォーム HogeFormEx を表示する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  HogeFormEx.Left := Self.Left + 150;
  HogeFormEx.Top  := Self.Top - 100;
  HogeFormEx.Show;
end;

//=============================================================================
//  フォーム HogeFormEx の色を変更
//  このイベントのコードは HogeFormEx の Button1 をクリックした時も実行される
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LColor : TColor;
begin
  LColor := RGB(Random(255), Random(255), Random(255));
  HogeFormEx.Color := LColor;
end;

//-----------------------------------------------------------------------------
//  HogeFormEx の位置が変更された時に実行されるメソッド
//  Memo1 に HogeFormEx の位置を表示
//-----------------------------------------------------------------------------
procedure TForm1.ReceiveFormMove(APos: TPoint);
begin
  Memo1.Lines.Add('Left : ' + IntToStr(APos.X));
  Memo1.Lines.Add('Top  : ' + IntToStr(APos.Y));
end;

end.




06_循環参照とその回避


フォルダ [06_Circular_Rreferenc] のプロジェクト を IDE で開いて [F9] でコンパイルを開始します。すると、次のような循環参照のエラーが発生してコンパイルできません。

[dcc32 致命的エラー] HogeFormExUnit.pas(12): F2047 ユニット 'Unit1' への循環参照です


このサンプルの、メインフォームから表示するフォーム HogeFormEx のコードは以下のようになっています。
uses 部でメインフォームのユニット Unit1 を参照しています。

unit HogeFormExUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Unit1;


メインフォームのユニットの uses 部には、HogeFormEx のユニット名 HogeFormExUnit があります。
この状態でコンパイルを開始すると、コンパイラは、参照している HogeFormExUnit を見に行きます。HogeFormExUnit にいくと、Unit1 を参照しているので Unit1 を見に行きます。Unit1 に行くと、HogeFormExUnit があります。このように参照が循環して、参照関係が解決できなくなります。その結果、循環参照のエラーとなります。

循環参照を回避するには、implementation 部の uses 部に (uses 部がなければ追加して ) 参照するユニット名を追加するようにします。そうすると、implementation 部のコードだけがそのユニットを参照するようになります。implementation 部だけが対象になるので、interface 部ではそのユニットの機能は利用できません。


unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  HogeFormExUnit;


[備考 1]
循環参照の可能性がないユニットでも、そのユニットを追加する必要性を強調する場合、implementation 部の uses に追加することがあります。
ユニット内で定義されている型や定数値、あるいはプロパティ等が interface 部で必要な場合は、interface 部の uses に記述することになります。


[備考 2]
循環参照な限らず、コンパイラが問題を解決できない場合、[F9] でコンパイル・実行すると IDE そのものが閉じてしまうことがあります。
その場合は、再度プロジェクトを開いて、[Ctrl] + [F9] でコンパイルするか、[Shift] + [F9] でビルド (再構築) してエラーを確認します。どとらのキー操作もコンパイルはしますが、EXE は実行しません。





07_リポジトリを利用したフォームの再利用


汎用的なフォームはリポジトリに登録しておいて、そのコピー、あるいは継承クラスのフォームを使用するようにすると便利なことがあります。リポジトリに登録するフォームのファイルは、ライブラリパスの通った場所に置いておきます。
[コピー] して利用する場合も、[継承] して利用する場合も、自動的にプロジェクトに追加されます。プロジェクトマネージャでユニット名を設定して、保存して利用します。


リポジトリに登録されているフォームを利用する例です。
[ファイル] [新規作成] [その他] のダイアログで [Delphi ファイル] の [デュアルリストボックス] を選択し、[コピー] して利用するサンプルです。コピーしたファイルは、プロジェクトのフォルダ直下の [Test7_Units] フォルダに保存しています。コピーですから、自由に改変して利用できます。


図7
リポジトリに登録されているフォームを使用する例

リスト5
リポジトリに登録されているフォームを使用する例
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }

  end;

var
  Form1: TForm1;

implementation

uses
  DualList;

{$R *.DFM}

//=============================================================================
//  リポジトリに登録されているフォームをコピーして利用する例
//
//  (1) [ファイル] [新規作成] [その他] のダイアログを表示
//  (2) [Delphi ファイル] から [デュアルリストボックス] を選択してコピー
//  (3) 名前を付けて保存で [Test7_Units] 内に保存
//  (4) uses に DualList を追加 
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if DualListDlg.ShowModal = mrOk then begin
  end;
end;

end.




08_フォーム上の全てのコントロールを別のフォームにコピー


他のプロジェクトのフォームのコードは、そのプロジェクト専用のコードです。その場合、フォームのコントロールル類だけをコピーして利用することも考えられます。その手順です。
コピー元のフォームのファイルを開いてアクティブにします。[Ctrl] + [A] のキー操作でコントロールを全て選択してから [Ctrl] + [C] でコピーします。


図8
フォーム上の全コントロールをコピー
  • [Ctrl] + [A] で全選択
  • [Ctrl] + [C] でコピー

新規にフォームを作成して [Ctrl] + [V] のキー操作で貼り付けます。


図9
新規にフォームを作成
図10
[Ctrl} + [V] で貼り付け、名前を付けて保存




09_実行時にフォームを作成して表示する関数


Delphi の InputQuery 関数は、実行時にフォームを新規に作成して表示します。フォームに配置するコントロール類も実行時に生成します。純粋な意味でのフォームの再利用ではありませんが、単純なフォームであればこのようなフォームの利用も考えられます。
このサンプルは自前の InputQuery 関数を実行します。このように自前でフォームを生成すれば、表示位置やフォントサイズも自由に設定できます。このサンプルでは、フォームに配置するコントロールの表示位置とサイズを、文字のサイズを元にして MulDiv 関数で計算していますが、その必然性は全くありません。

フォームを新規に作成するのではなく、フォームのファイルを別に用意しておいて、そのフォームを実行時に生成することも考えられます。そうすれば、コントロール類のイベントやメソッド類を使用する複雑な機能を持たせることも容易です。

InputQuery 関数のようにフォームを使用した場合は、引数で値を渡したり、引数または戻り値で結果を取得することになります。一方、フォームそのものを直接表示する場合は、フォームのクラスとしての機能を使用します。フォーム、あるいはフォーム上のコントロール類のプロパティやメソッドで値の受け渡しをすることになります。前掲の、リポジトリに登録されているフォームを使用するサンプルはその例です。



図11
実行時の画面

リスト6
実行時に新規にフォームを作成して表示
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function InputQuery(const ACaption, APrompt: string; var Value: string): Boolean;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  自前の InputQuery 関数を実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue : string;
begin
  LValue := '123';
  if InputQuery('値の入力', '値', LValue) then begin
  end;
end;

//-----------------------------------------------------------------------------
//  文字 (アルファベット) のサイズを取得する関数
//  Result.x は A.Z,a..z の文字の平均文字幅
//  Result.y は A..Z,a..z の文字の最大高さ
//-----------------------------------------------------------------------------
function GetAveCharSize(Canvas: TCanvas): TPoint;
var
  LIndex  : Integer;
  LBuffer : array[0..51] of Char;
begin
  // 文字配列に A..Z,a..z の文字を全てセット
  for LIndex := 0 to 25 do begin
    LBuffer[LIndex] := Chr(LIndex + Ord('A'));
  end;
  for LIndex := 0 to 25 do begin
    LBuffer[LIndex + 26] := Chr(LIndex + Ord('a'));
  end;

  GetTextExtentPoint(Canvas.Handle, LBuffer, 52, TSize(Result));
  Result.X := Result.X div 52;
end;

//-----------------------------------------------------------------------------
//  実行時にフォームを新規作成して表示する関数の例
//  フォームに配置するコントロールも実行時に生成すする
//  Delphi の InputQuery 関数のコード (若干改変)
//
//  コントロールの位置とサイズを MulDiv 関数を使用して文字数を基準にして計算
//  自分でコーディングする場合は、それにこだわる必要はない
//-----------------------------------------------------------------------------
function InputQuery(const ACaption, APrompt: string;  var Value: string): Boolean;
var
  LForm         : TForm;
  LPrompt       : TLabel;
  LEdit         : TEdit;
  LButtonOk     : TButton;
  LButtonCancel : TButton;
  LDialogUnits  : TPoint;
  LButtonTop    : Integer;
  LButtonWidth  : Integer;
  LButtonHeight : Integer;
begin
  Result := False;

  LForm := TForm.Create(Application);
  try
    LForm.Canvas.Font := LForm.Font;
    LDialogUnits := GetAveCharSize(LForm.Canvas);

    // 生成するフォームのプロパティ
    LForm.BorderStyle := bsDialog;
    LForm.Caption     := ACaption;
    LForm.ClientWidth := MulDiv(180, LDialogUnits.X, 4);
    LForm.Position    := poScreenCenter;

    // TLabel の生成
    LPrompt := TLabel.Create(LForm);
    LPrompt.Parent  := LForm;
    LPrompt.Caption := APrompt;
    LPrompt.Left    := MulDiv(8, LDialogUnits.X, 4);
    LPrompt.Top     := MulDiv(8, LDialogUnits.Y, 8);
    LPrompt.Constraints.MaxWidth := MulDiv(164, LDialogUnits.X, 4);
    LPrompt.WordWrap := True;

    // TEdit の生成
    LEdit := TEdit.Create(LForm);
    LEdit.Parent    := LForm;
    LEdit.Left      := LPrompt.Left;
    LEdit.Top       := LPrompt.Top + LPrompt.Height + 5;
    LEdit.Width     := MulDiv(164, LDialogUnits.X, 4);
    LEdit.MaxLength := 255;
    LEdit.Text      := Value;
    LEdit.SelectAll;

    // 生成する TButton の位置の値
    LButtonTop    := LEdit.Top + LEdit.Height + 15;
    LButtonWidth  := MulDiv(50, LDialogUnits.X, 4);
    LButtonHeight := MulDiv(14, LDialogUnits.Y, 8);

    // OK ボタンの生成
    LButtonOk := TButton.Create(LForm);
    LButtonOk.Parent     := LForm;
    LButtonOk.Caption    := 'OK';
    LButtonOk.ModalResult := mrOk;
    LButtonOk.Default    := True;
    LButtonOk.SetBounds(MulDiv(38, LDialogUnits.X, 4), LButtonTop,
      LButtonWidth, LButtonHeight);

    // キャンセルボタンの生成
    LButtonCancel := TButton.Create(LForm);
    LButtonCancel.Parent      := LForm;
    LButtonCancel.Caption     := 'キャンセル';
    LButtonCancel.ModalResult := mrCancel;
    LButtonCancel.Cancel      := True;
    LButtonCancel.SetBounds(MulDiv(92, LDialogUnits.X, 4), LEdit.Top + LEdit.Height + 15,
      LButtonWidth, LButtonHeight);

    LForm.ClientHeight := LButtonCancel.Top + LButtonCancel.Height + 13;


    // フォームをモーダル表示
    if LForm.ShowModal = mrOk then begin
      Value  := LEdit.Text;
      Result := True;
    end;
  finally
    FreeAndNil(LForm);
  end;
end;

end.


[備考]
このサンプルが生成する TButton は ModalResult を設定しています。したがって、TButton の OnClick イベントのコードはありません。





10_実行時に生成して表示するフォームの解放処理


添付のサンプルは全て自動生成として動作するようになっています。Create メソッドでフォームのインスタンスを生成しなくても動作します。自動生成のフォームは、アプリケーションが自動的に解放処理を行います。


implementation

uses
  HogeFormUnit;

{$R *.DFM}

//=============================================================================
//  フォーム HogeForm をモードレス表示
//  HogeForm が自動生成の場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  HogeForm.Left := Self.Left + 100;
  HogeForm.Top  := Self.Top - 100;

  HogeForm.Show;
end;

//=============================================================================
//  フォーム HogeForm をモーダル表示
//  HogeForm が自動生成の場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  HogeForm.Left := Self.Left + 100;
  HogeForm.Top  := Self.Top - 100;

  HogeForm.ShowModal;
end;


[プロジェクト] [オプション] のダイアログの [フォーム] で [使用可能なフォーム] にした場合は、実行時に Create メソッドでそのフォームのインスタンスを生成して使用することになります。
アプリの開発では、メインフォーム以外のフォーム全てを実行時に生成して使用することもあります。

プロジェクトオプションのダイアログのショートカットキーは [Ctrl] + [Shift] + [F11] です。



図12
実行時生成フォーム

実行時に生成してモーダル表示するフォームの場合は、以下のように、フォームを閉じた後に FreeAndNil 手続きで解放処理ができます。


implementation

uses 
  HogeFormUnit;

{$R *.DFM}

//=============================================================================
//  フォーム HogeForm をモーダル (ShowModal メソッドで) 表示
//  Create メソッドの引数は nil でもよい
//========================f=====================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  HogeForm := THogeForm.Create(Self);
  try
    HogeForm.Left := Self.Left + 100;
    HogeForm.Top  := Self.Top - 100;

    HogeForm.ShowModal;
  finally
    FreeAndNil(HogeForm);
  end;
end;


実行時にフォームを生成して Show メソッドでモードレスで表示する場合、以下のようにります。


implementation

uses 
  HogeFormUnit;

{$R *.DFM}

//=============================================================================
//  フォーム HogeForm をモードレス (Show メソッドで) 表示
//  Create メソッドの引数を nil にしないこと
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if HogeForm = nil then begin
    HogeForm := THogeForm.Create(Self);
    HogeForm.Left := Self.Left + 100;
    HogeForm.Top  := Self.Top - 100;
  end;

  HogeForm.Show;
end;


モードレスで表示したフォームを解放処理するには、そのフォームの OnClose イベントハンドラに以下のコードを追加します。nil を代入しておくことで、このフォームのインスタンスの有無が確認できます。nil を代入している変数は、表示するフォームのユニットで宣言されている変数です。別の名前で THogeForm のインスタンスを生成した場合は、そのインスタンスは解放されません。また、nil にもなりません。

自動生成のフォームの OnClose イベントにこの解放処理があると、トラブル発生の原因になります。



リスト7
Show メソッドで表示したフォームの解放処理
var
  HogeForm: THogeForm;

implementation

{$R *.dfm}

{ THogeForm }

//=============================================================================
//  フォームが閉じる時の処理
//  このフォーム自身のメモリを解放して nil を代入しておく
//  nil を代入しておくことでインスタンスの有無の判定が可能になる
//  このフォームでは、TControl(Sender).Name の値は常に HogeForm
//=============================================================================
procedure THogeForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
  HogeForm := nil;
end;


フォームをモードレス表示する場合、Create メソッドの引数を nil にすると、そのフォームを表示したままメインフォームを閉じた時、そのフォームのメモリは解放されず、メモリリークが発生します。
Delphi は、生成されたフォームのインスタンスを内部で管理しています。下図は、メインフォームが破棄される時にまだ解放されていない、オーナを持つフォームの一覧です。これらは、フォームが破棄される時に自動的に解放処理されます。オーナを持たないフォームは自動では解放処理されません。オーナを持たないフォームとは、Create メソッドの引数を nil にして生成したフォームのことです。



図13
オーナを持つフォームの一覧
  • Buton1 を何回かクリック
  • 表示したフォームを閉じないでメインフォームを閉じる
  • ( ) 内はオーナ (Create メソッドの引数)

//=============================================================================
//  フォーム HogeForm をモードレス表示
//  Button1 をクリックした回数だけ THogeForm のフォームが生成される
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LForm : THogeForm;
begin
  LForm := THogeForm.Create(Self);
  LForm.Left := Self.Left + 150;
  LForm.Top  := Self.Top - 300;

  LForm.Show;
end;

//=============================================================================
//  このフォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  LIndex : Integer;
  LForm  : TForm;
  LText  : string;
begin
  LText := 'オーナを持つフォーム一覧' + sLineBreak + sLineBreak;
  for LIndex := Screen.FormCount -1 downto 0 do begin
    LForm := Screen.Forms[LIndex];
    if LForm.Owner <> nil then begin
      LText := LText + LForm.Name + '  ( ' + LForm.Owner.Name + ' ) ' + sLineBreak;
    end;
  end;
  MessageBox(Handle, PChar(LText), 'フォーム一覧', MB_ICONINFORMATION);
end;