Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2013年01月18日] [更新 2017年11月15日] Delphi サンプルプログラム集
122_複数のフォームを使用するプロジェクト ( 入門者用 )
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
122_PluralForms.zip [5,283 KB] 2017年11月15日版 (EXE 同梱)



  • 2013年06月08日
  • public 部と published 部の違い等の記事を追加
  • 2017年11月15日
  • [モーダル表示とモードレス表示] は CreateParams の処理方法を先に説明し、MainFormOnTaskbar の設定による方法を後にした。




複数のフォームを使用するプロジェクト


メインフォーム以外のフォームを必要とするプロジェクトを作成する時の参考サンプルです。
フォームが 1 つだけのアプリケーション作成の知識はあるものとします。
別のフォーム、他のフォームのメンバ (プロパティやメソッド) の値を取得したり、値を設定、セット、受け渡しする時等の参考用です。

他のサンプルでは、主に、どのような動作をするかをまず図で示してから、その動作をするためのコード示すようにしていますが、このページのサンプルは、最初にコードを示して、それを実行した結果としての図を示すようにしています。

当然ですが、入門者用の記事ですから、読むだけでなく、実際に順番に操作することを前提にしてサンプルと記事を書いています。



[備考 1]
Delphi に限らず、1 つのプロジェクトは 1 つのプログラム単位です。1 つのプロジェクトから 1 つの EXE あるいは DLL ができます。たとえ同じ Delphi のプロジェクトであっても、別のプロジェクトで作成した EXE は全く別のアプリケーションとなります。
Delphi の IDE では、複数のプロジェクトを開いて編集ができますが、異なるプロジェクトの間で、本ページの方法が使えるわけではありません。


[備考 2]
ツールバーにコンポーネントパレットを表示するようにしておくと便利です。


[備考 3]
複数のフォームを使用するプロジェクトで、メインフォームではないフォームを「サブフォーム」と書いている記事を見かけます。当サイトの記事では、Delphi のオンラインヘルプの表記にしたがって、それらを「子フォーム」としています。
子フォームを補助的な操作用に使用することから「サブフォーム」という用語が使われているものと思われます。





01_メインフォーム以外のフォームの作成方法


まず、新規にプロジェクトを作成します。新規にプロジェクトを作成するには、[ファイル] メニューから [VCL フォームアプリケーション] を選択します。

プロジェクトにフォームを追加するには、次の図のように、メニューの[ファイル] の [新規作成] から [フォーム] を選択します。いくつでも追加できます。
追加すると、フォームの名前は、Form2, Form3, ... となります。ユニット名は Unit2, Unit3, ... となります。

新規に VCL フォームアプリケーションでプロジェクトを作成した時のフォームをメインフォーム、追加作成したフォームを「子フォーム」と言いいます。実行時の子フォームは「子ウィンドウ」と呼ばれることもあります。



図1
フォーム追加

下図のように [Unit2] というタブが追加され、Form2 が表示されます。
表示フォームを切り換えるには、このタブを選択します。もしフォームではなくコードが表示されている場合は [F12] でフォームとコード (コードエディタ) の表示を切り換えます。

フォームとコードエディタの切り換え等は [プロジェクトマネージャー] でも操作できます。
また、[Shift] + [F12] でフォームの一覧、[Ctrl] + [F12] でユニットの一覧ダイアログを表示して、表示するフォームまたはそのフォームのユニットを選択できます。



図2
Unit2 ができる

フォームを追加すると、プロジェクトのソースは以下のようになります。
uses に Unit1.pas と Unit2.pas が追加され。これらが、このプロジェクトに含まれていることを意味しています。Unit1 in と Unit2 in の後ろの部分は、これらのファイルのパス名です。今回はプロジェクトファイル (.dpr) と同じディレクトリ (フォルダ) 内にあるのでこのようになつています。

begin 以降の CreateForm のコードで Form1 と Form2 が生成されます。CreateForm(TForm1, Form1) は、TForm1 というクラスのフォームを、Form1 という名前で、そのインスタンス (実体) を生成することを意味しています。

そして、Application.Run でメインフォームのユニットである Unit1 のコードが実行されます。



リスト1
プロジェクトのソースコード
メニューの [プロジェクト] [ソースの表示] で表示する
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm2, Form2);
  Application.Run;
end.


[備考]
アプリケーションの起動時、つまり EXE を実行すると最初に表示するフォームをメインフォームと言います。
追加した子フォームをメインフォームにすることもできます。
メニューの [プロジェクト] [オプション] でダイアログを表示します。左ペインの [フォーム] を選択して、[メインフォーム] のリストからメインフォームにするフォームを選択します。





02_使用するフォームのユニット名を uses 部に追加する


以降の全てのサンプルでは、追加したフォームを、メインフォームから表示します。
そのためには、メインフォームの Unit1 で、それらのフォームのユニット名を uses 部に追加しておかなければなりません。
ここでは、implementation に追加します。interface 部で追加したフォームを参照する必要がなければ、implementation 部の uses に追加しておくと、循環参照エラーが発生するのを防止できる効果があります。


リスト2
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Unit2;  //これを追加する

{$R *.dfm}

end.


次の図のような方法でも uses 部にユニット名を追加できます。


図3
[ファイル][ユニットを使う]
図4
Unit2 を選択して [OK]




03_アプリ生成時に他のフォームも生成  -  自動生成


これでプロジェクトにフォームを追加することができるようになりました。実際にやってみます。
  • (1) 新規にプロジェクトを作成
  • (2) フォームを 1 つ追加
  • (3) uses 部に Unit2 を追加
  • (4) メインフォームに TButton を追加して、そのクリックイベントを下のコードとする
このコードは、Form2 の表示位置を設定して、ShowModal メソッドで Form2 を表示します。


リスト3
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2;  

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Left := Self.Left - 50;
  Form2.Top  := Self.Top  + 80;

  Form2.ShowModal;
end;

end.


コンパイルして実行します。Button1 をクリックすると下図のようになります。
Form1 と Form2 のサイズは、デフォルトの値でなく、変更しています。


図5
実行画面
  • このサンプルでは、Form2 を閉じないとメインフォームには戻れない

上のコードには、Form2 という変数の定義文がありません。Form2 の変数の定義は、Unit2 で以下のように、グローバル変数として定義されています。
グローバル変数は、その変数が定義されているユニット名を uses 部に追加すれば、どのユニットからも参照できます。

var
  Form2 : TForm2;

Form2 は Unit2 にありますから、厳密には以下のように、Unit2.Form2 です。
この Unit2 のことを (ユニット) スコープ名とも言いますが、同じ名前のフォームは複数作成することはできません。したがって、Unit2 を付ける必要はありません。

フォーム以外の他のユニットにある関数類も、重複していなければユニット名を付ける必要はありません。Delphi が提供している関数等も、通常はユニット名を付けないで使用しています。
ただし、どのユニットにある関数なのかを、明らかにした方がいい場合は、ユニット名付けることがあります。それは、プログラマ自身の判断ということになります。

同じ名前の関数が複数のユニットにあって、それらのユニットを同時に使用する場合は、ユニット名を付けないと区別がつきません。


procedure TForm1.Button1Click(Sender: TObject);
begin
  Unit2.Form2.Left := Self.Left - 50;
  Unit2.Form2.Top  := Self.Top  + 80;

  Unit2.Form2.ShowModal;
end;


[備考 1]
上のコードに Self というのがあります。Self は和訳すると自分自身です。
メソッドやイベントの呼出し元のクラスのインスタンス (自分) を表す変数です。上のコードの場合は Form1 のイベント内で使用していますから、Form1 のことです。


[備考 2]
本サンプルでは子フォームを ShowModal というメソッドで表示しています。フォームを表示するメソッドとして Show というメソッドもあります。この後のサンプルで使用しています。
フォームを非表示にするメソッドは Hide です。フォームを非表示にする Visible というプロパティもあります。






04_メインフォームを閉じるとアプリケーション終了


複数のフォームを使用するアプリケーションでは、メインフォームを閉じると、アプリケーションが終了することになります。したがって、メインフォームの OnClose, OnCloseQuery, OnDestroy イベント等に、アプリケーション終了時の処理を実行させることができます。
逆に、アプリケーション開始時の処理には、メインフォームの OnCreate イベントを使用することができます。ただし、OnCreate イベントで時間のかかる初期化処理を行うと、EXE の起動が遅くなることがあります。

一方、追加したフォームを閉じてもアプリケーションは終了しません。その追加したフォームが閉じるだけです。




05_アプリ実行時にフォームを生成  -  使用可能フォーム


新規にプロジェクトを作成して、上のサンプルと同じように、1 つだけ別のフォーム作成し、同じ作業を行いますが、Button1 のクリックイベントを次のようにします。
このテストは、新規にプロジェクトを作成しなくても、上のテストで使用したプロジェクトを使用しても構いません。


リスト4
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  //TForm2というクラスのフォームのインスタンスを生成する
  Form2 := TForm2.Create(Self);
  try
    //表示位置を設定
    Form2.Left := Self.Left - 50;
    Form2.Top  := Self.Top  + 80;

    //ShowModalメソッドで表示
    Form2.ShowModal;
  finally
    FreeAndNil(Form2);
  end;
end;

end.


続いて、今度は、下図のように、[プロジェクト] [オプション] でダイアログを表示し、[フォーム] を選択し、[自動生成フォーム] にある Form2 を [>] ボタンで [使用可能フォーム] に移動します。


図6
フォームの設定
  • Form2 を [→] ボタンをクリックして、[使用可能なフォーム] に移動する

[OK] ボタンでダイアログを閉じたら、[F9] を押下して実行します。実行結果は、前のサンプルと全く同じとなります。

プロジェクトのソースを見ると、次のようになっています。CreateForm では Form2 を生成していません。つまり、Form2 は、このプロジェクト実行開始時には生成されないことになります。
そこで、Form2 を使用する時は、実行時に Form2 のインスタンス (実体) を生成します。



リスト5
プロジェクトのソースコード
メニューの [プロジェクト] [ソース表示] で表示する
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


Form2 のインスタンス (実体) を実行時に生成するコードが、


  Form2 := TForm2.Create(Self);

です。もし、このコードを書かないでコンパイルして実行すると、下図のように、アドレスに対する読み取り違反のエラー (メモリアクセスの違反) が発生します。



図7
フォームを Create しない時のエラー

デバツガの設定によっては、下図のダイアログも現れることもあります。



図8

フォームだけでなく、TButton 等のコンポーネント、コントロールも、設計時にはフォームに配置しないで、実行時にそのインスタンス (実体) を生成して利用できます。
これを、[実行時生成] とか [動的生成] と言っています。



TForm2 のインスタンスを生成する際、次の AForm のように別の名前を定義して生成することもできます。TButton 等も、いろいろな名前で使用できるのと同じです。
この方法は、この後のサンプルでも使用しています。

procedure TForm1.Button1Click(Sender: TObject);
var
  AForm : TForm2;
begin
  //TForm2というクラスのフォームのインスタンスをAFormという名前で生成する
  AForm := TForm2.Create(Self);
  try
    //表示位置を設定
    AForm.Left := Self.Left - 50;
    AForm.Top  := Self.Top  + 80;

    //ShowModalメソッドで表示
    AForm.ShowModal;
  finally
    FreeAndNil(AForm);
  end;
end;


[備考]
業務アプリの開発では、時には数十のフォームを使用することがあります。この時、フォームをデフォルトの [自動生成] にしていると、作成した EXE を起動する時に全てのフォームの初期化が行われます。そのため、EXE の起動が遅くなることがあります。
メインフォーム以外のフォームを [使用可能なフォーム] にして実行時に生成すれば、この現象を回避することができる場合があります。リソース (メモリ) の節約にもなります。
どのフォームを [自動生成] や [使用可能なフォーム] にするかは、そのフォームの処理内容やメインフォームとの関係によって決めることになります。子フォームから更に別のフォームを使用する場合も同じです。
後述の [プロジェクトにないフォームの利用...] の説明のように、子フォームをプロジェクトに追加しないで利用することもあります。





06_モーダル表示とモードレス表示 - ShowModal と Show メソッド


いままでのサンプルでは、モーダルと呼ばれる形式で別のフォームを表示していました。
別のフォームを表示する際、モードレス形式というのもあります。この両方の表示形式で、別のフォームを表示して、それらがどんなのかを確認するテストです。

新規にプロジェクトを作成します。そして、フォームを 2 つ作成します。もちろん、uses には Unit2 と Unit3 を追加します。以下に参考リンクを示していますが、実際にやってみて、体で覚える方が確実で早道です。

ShowModal で表示したフォームを閉じた場合、TModalResult 型のプロパティの値でその戻り値を取得できます。後述の [プロジェクトにないフォームの利用と ModalResult] も参考にしてください。



Button1 と Button2 のクリックイベントを各々次のようにします。


リスト6
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2, Unit3;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  Form2をモーダルで表示
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Left := Self.Left - 50;
  Form2.Top  := Self.Top  + 80;

  Form2.ShowModal;
end;

//-----------------------------------------------------------------------------
//  Form2をモードレスで表示
//-----------------------------------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
begin
  Form3.Left := Self.Left - 50;
  Form3.Top  := Self.Top  + 80;

  Form3.Show;
end;

end.


コンパイルして実行します。次の図のようになります。


図9
Button1 をクリック
  • Form2 を ShowModal メソッドでモーダル表示する
  • Form2 を閉じないとメインフォームには戻れない
図10
Form2 を閉じて Button2 をクリック
  • Form3 を Show メソッドでモードレス表示
  • メインフォーム、From3 のどちらにもフォーカス移動ができる
  • メインフォームは子フォームの前面には表示できない。これが Windows Vista 以降の標準の動作仕様


Delphi は、Delphi 2007 で Windows Vista に対応しました。Windows Vista 以降では、メインフォームは、子フォームよりも前面に表示できないのが標準の仕様となっています。この仕様により、メインフォームと子フォームを明確に区別することができます。

メインフォームを子フォームよりも前面に表示できるようにするには、子フォームの CreateParams メソッドの処理コードを以下のように追加します。追加方法は、[メッセージ処理メソッドの作成] を参考にしてください。モードレス表示するフォームは Form3 なので Unit3 に追加します。

この方法でメインフォームを前面に表示できるのは Show メソッドで子フォームを表示した時だけです。ShowModal メソッドで表示したモーダル表示の子フォームの前面には表示できません。



リスト7
unit Unit3;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm3 = class(TForm)
  private
    { Private 宣言 }
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  public
    { Public 宣言 }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

{ TForm3 }

procedure TForm3.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.WndParent := Application.Handle;
end;

end.


コンパイルして EXE を実行します。Button2 をクリックして子フォームを表示します。


図11
子フォームを Show メソッドでモードレス表示
  • メインフォームをクリックすると、メインフォームが子フォームの前面に表示される
  • この挙動は Windows Vista 以降の標準仕様ではない

[備考]
CreateParamsメソッドを使用すると、以下のサンプルのようにタスクバーへのアイコン表示を変更したりフォームのスタイルを変更することができます。
OnCreate イベントでは既にフォームが生成されていますが、CreateParams はフォームの生成時に自動的に実行されるメソッドです。そのため以下のサンプルのような処理が可能となっています。





07_モードレス表示と MainFormOnTaskbar の設定


前項では、メインフォーム以外のフォームをモードレス表示にした時に、フォームのフォーカス移動と前面表示のために、CreateParams メソッドの処理を追加しました。
以下のように、プロジェクトのソースコードの MainFormOnTaskbar の値を False にすることでも同じ動作を実現できます。この方法は Delphi 2007 以降で使用できます。

この TApplication.MainFormOnTaskbar プロパティは、Delphi 2007よりも前のバージョンには実装されていません。Delphi 2007 よりも前では、MainFormOnTaskbar := False と同じです。
MainFormOnTaskbar の値が、本来の True でないと実現できない処理もあります。その場合は、前項の方法でメインフォームを前面に表示するようにします。



リスト8
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2},
  Unit3 in 'Unit3.pas' {Form3};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := False;  //Falseにする
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm2, Form2);
  Application.CreateForm(TForm3, Form3);
  Application.Run;
end.


MainFormOnTaskbar の値が False でも、メインフォームと子フォームの Z オーダを Windows Vista 以降の仕様にすることができます。それには、前項で説明した CreateParams メソッドを子フォームに実装します。
CreateParams メソッドで、次のように Params.WndParent の値をメインフォームのハンドルにします。これで、子フォームを Show メソッドで表示した時に、メインフォームをアクティブにしてフォーカスを移動しても、メインフォームは子フォームの前面に表示できなくなります。

この Params.WndParent の値による設定は、MainFormOnTaskbar の値に関係なく有効です。前項のサンプルのように Application.Handle (アプリケーションのハンドルの値) にすれば、メインフォームを子フォームの前面に表示できるようになります。



リスト9
procedure TForm3.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.WndParent := Application.MainForm.Handle;
end;


[備考]
MainFormOnTaskbar の値がデフォルトの True の時は、タスクバーには文字通りメインフォームのアイコンが表示されます。False の時はアプリケーションのアイコンが表示されます。
アプリケーションのアイコン (EXE のアイコン) は、IDE のメニューから [プロジェクト] [オプション] [アプリケーション] で設定します。アプリケーションのアイコンはマルチアイコンです。メインフォームのアイコンは TForm の Icon プロパティで変更できます。
Icon プロパティが未設定の場合は、アプリケーションのアイコンの中から適当なサイズのアイコンを、拡大あるいは縮小してフォームの左上とタスクバーに表示します。





08_メインフォームを表示する前に別のフォームを表示


アプリの実行開始時に、メインフォームを表示する前に、別のフォームを表示する例です。
新規にプロジェクトを作成したら、フォームを 1 つ追加します。追加したフォームは [使用可能なフォーム] にしておきます。
次に、メインフォームの OnCreate イベントを作成して、そのイベントのコードを次のようにします。
このサンプルでは、メインフォームの OnCreate 内で位置の取得はできません。そこで、具体的な値を設定しています。


リスト10
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  メインフォーム生成時に別のフォームを表示
//  メインフォームの位置は取得できないので、Form2の位置は具体的な値にする
//  Form2は、自動生成から除外して、使用可能フォームにしておく
//-----------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
var
  AForm : TForm2;
begin
  AForm := TForm2.Create(Self);
  try
    AForm.Left := 500;
    AForm.Top  := 300;

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

end.


実行した時の画面です。EXE を起動すると、まず、Form2 が表示されます。この Form2 を閉じるとメインフォームが表示されます。


図12
EXE を起動すると Form2 が表示される
図13
Form2 を閉じるとメインフォーム表示




09_メインフォームの表示直後に別のフォームを表示


アプリの実行開始時に、メインフォームを表示した直後に別のフォームを表示する例です。
新規にプロジェクトを作成したら、フォームを 1 つ追加します。追加したフォームは [自動生成フォーム] のままにしておきます。
次に、CM_SHOWINGCHANGED メッセージの処理コードを追加します。追加方法は、[メッセージ処理メソッドの作成] を参考にしてください。


リスト11
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private 宣言 }
  protected
    procedure CMShowingchanged(var Message: TMessage);
      message CM_SHOWINGCHANGED;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  CM_SHOWINGCHANGEDメッセージの処理
//  CM_SHOWINGCHANGEDは表示の状態が変化した時に発生する
//  非表示になる時も発生するので、Visibleプロパティで判断する
//-----------------------------------------------------------------------------
procedure TForm1.CMShowingchanged(var Message: TMessage);
begin
  inherited;  //必ず必要

  if Self.Visible then begin
    Form2.Left := Self.Left - 50;
    Form2.Top  := Self.Top  + 80;

    Form2.ShowModal;
  end;
end;

end.


準備ができたら、コンパイルして実行します。


図14
EXE 起動直後の画面
  • メインフォーム表示直後に Form2 を表示
  • Form2 を閉じないと、メインフォームにはフォーカス移動ができない




10_他のフォームのユニットの変数やメソッド類を使う


複数のフォームを使用するアプリケーションでは、他のフォームに配置したコンポーネントを操作したり、他のフォームのユニットの変数の値を取得、設定したり、あるいは、関数等を実行したりすることがあります。その簡単なテストです。

新規にプロジェクトを作成したら、メインフォームとなる Form1 に TButton を 1 つ、TPanel を 2 つ配置します。


図15
メインフォームの設計時画面

次に、別のフォームを 1 つ作成します。これは Form2 となります。
この Form2 に TPanel を 1 つ配置して、Caption プロパティの値を、'テスト文字列' とします。
public 部に、TestStr という文字列型の変数を定義します。また、PutTextString というメソッドを作成して、そのコードを次のようにします。
メソッド類の作成は、[関数等の宣言文から実装部 ( スケルトン ) を作成] や [実装部から関数等の宣言文を作成]を参考にしてください。


[備考 1]
自作のメソッドや関数類の定義は、private部 や public 部に書きます。そうすれば、オブジェクトインスペクタで設定したイベント (もメソッドの一種です) と区別できます。また、他のユニットから、それらのメソッド、関数類を参照する際のアクセス制限も可能となります。このような慣習には従った方が無難です。


リスト12
メインフォームから表示する Form2 のユニットのコード
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm2 = class(TForm)
    Panel1: TPanel;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    TestStr : String;
    procedure PutTextString;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

{ TForm2 }

//-----------------------------------------------------------------------------
//  文字列型の変数TestStrに文字列を代入するメソッド
//-----------------------------------------------------------------------------
procedure TForm2.PutTextString;
begin
  TestStr := 'DELPHI XE';
end;

end.


次にメインフォームの Button1 クリックのイベントを次のようなコードにします。

このコードは、Form2 の PutTextString メソッドを実行します。このメソッドは TestStr という文字列型の変数に文字列を代入します。
本サンプルでは、メインフォームからこの TestStr の値を取得して表示します。また、Form2 に配置している Panel1 の Caption の文字列を取得して表示します。


リスト13
メインフォームのユニットのコード
Form2 のメソッドを実行して、Form2 の変数の値を取得して表示する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Panel1.Caption := '';
  Panel2.Caption := '';

  //Form2のPutTextStringメソッドを実行
  Form2.PutTextString;
  //Form2の変数であるTestStrの値をPanel1に表示
  Panel1.Caption := Form2.TestStr;

  //Form2のPanel1のCaptionの値を表示
  Panel2.Caption := Form2.Panel1.Caption;
end;

end.


実行結果です。


図16
EXE 起動直後の画面
図17
Button1 をクリック
  • 上の TPanel は Form2 の PutTextString メソッドの実行結果が格納された TestStr の値
  • 下の TPanel は Form2 上に配置したPanel1 の Caption プロパティの値

このサンプルのように、他のフォームあるいはユニットの変数の取得や設定ができます。他のフォームに配置してあるコンポーネントの操作も可能です。またメソッドや関数も実行できます。

ただし、他のユニットの変数等の操作が全てできるわけではありません。次のように、それらの変数やメソッドあるいは関数類がどこに定義されているかにもよります。


リスト14
type
  TMyClass = class(TObject)
    // ここには書かないこと(Delphiが使用する特別な領域)
  private
    // ここで宣言したた変数やメソッドは同じユニット内だけでアクセス可能
    //  他のユニットからはアクセスできない
  protected
    // ここで宣言した変数やメソッドは同じユニット内だけでアクセス可能
    // 他のユニットからはアクセスできない
  public
    // ここで宣言した変数やメソッドはどのユニットからもアクセス可能
  published
    // ここで宣言した変数やメソッドはどのユニットからもアクセス可能
  end;


[備考 2]
他のフォーム等、他のクラスの変数メンバを使用する際、実用的には、それらの変数をプロパティとして実装します。そうすれば、それらのプロパティの値を取得したり、セットしたりする時に、特定の処理を行うことができます。呼出し側で処理しなくても、呼び出される側で処理すれば、アプリケーションの動作仕様の変更にも柔軟に対応できるようになります。


[備考 3]
複数のフォームを使用するアプリケーションでは、共通に使用する定数や変数、あるいはメソッドが必要な場合があります。そのような場合、フォームがないユニットを作成して、そのユニットを uses して使用するのが一般的です。
そのユニット内の変数を使用する場合、ユニット名.変数名 という使い方をすることがあります。そうすると、その変数は、現在のユニットにはないことが明確に分かります。
あるいは、クラス型として実装すれば、クラスとしての機能が利用でき便利です。





11_private 部と protected 部の違い


どちらも他のユニットからも参照、アクセスできませんが、クラスの機能を拡張したり、仕様を変更した継承クラスを作成することによって、他のユニットからアクセス可能にすることができます。そのためには、継承したクラスで、public 部あるいは published 部へ「移動」するのですが、その方法はプロパティとメソッドとで違います。
また、protected 部のメンバーは、他のユニットで単に継承クラスを定義すれば、継承クラスの型にキャスト (型変換) することで、他のユニットからもアクセスできるようになります。private 部はまさにプライベートのままで、他のユニットからはアクセスできません。
上のコードでも、TMyClass は TObject を継承したクラスということになります。

Delphi では、コンポーネントパレットに登録するものを「コンポーネント」、そうでないものを単に「クラス」と言っていますが、実際にはどちらも「クラス」であることに間違いありません。コンポーネントパレットに登録できない「クラス」でも、便宜上「コンポーネント」と言う場合があります。あまり厳密に考える必要はないかも知れません。





12_public 部と published 部の違い


どちらも他のユニットから参照できます。主にプロパティやイベントの定義に使用します (実際にはイベントもプロパティの一種です)。
published 部で定義したメンバーは、そのクラスをコンポーネントとして登録すると、オブジェクトインスペクタに表示され、設計時に値を設定できるようになります。これらの設定値は、フォームの生成時、OnCreate イベントが発生する前に設定されます。

また、published 部のメンバーは、クラスのインスタンス (TButton ならば Button1 等) をファイルに保存すると、その設定値もファイルに保存されます。public 部の値は保存されません。




13_ローカル変数とグローバル変数


イベント内、あるいは関数やメソッド内で定義した変数は、ローカル変数です。
ローカル変数は、そのイベント内、あるいはメソッド内だけで有効です。他のメソッドでは使用できまません。

一方、上で説明した private 部, protected 部, public 部, published 部で定義した変数はグローバル変数です。ユニット内のどこからも参照できます。
public 部, published 部のグローバル変数は、上で説明したように、そのユニット内だけでなく他のユニットからも参照できます。つまり利用できます。
private 部で定義した変数は、その変数が実際に、そのユニット内で使用されているかを、コンパイル時にチェックしてくれます。

次のように、implementation 部で定義した変数類もグローバル変数ですが、この変数はこのユニット内の implementation 部だけで有効な変数となります。implementation 部で定義した変数や関数類は、その変数が実際にそのユニット内で使用されているかはチェックされません。
他のユニットからは参照されたくない場合や、コールバック関数と呼ばれる関数内で参照する変数に使われることがあります。



var
  Form1: TForm1;  //どのユニットからも使用可能なグローバル変数

implementation

{$R *.dfm}

var
  UTestText : String;  //ユニット内のimplementation部だけで使用可能なグローバル変数





14_プロジェクトにない既存フォームの利用と ModalResult


既存の、他のフォームあるいはユニットは、プロジェクトに追加されていなくても操作可能です。
このことを確認してみます。
上で使用したプロジェクトを変更していきます。

まず Form2 に TButton を 2 つ配置して、各々の Caption を図のようにします。
[OK] ボタンの Default プロパティを True にして、ModalResult プロパティを mrOkにします。[キャンセル] ボタンの Cancel プロパティを True に、ModalResult プロパティを mrCancel にします。オブジェクト・インスペクタで設定します。
この ModalResult の設定は、既存のフォームの利用とは関係ありません。 


図18
Form2 にボタンを 2 つ配置

次に、メニューの [プロジェクト] [プロジェクトから削除] でダイアログを表示します。
ここで Unit2 を選択して [OK] ボタンで終了します。
これで Unit2 のフォームはこのプロジェクトのフォームではなくなりました。このプロジェクトとは関係ない、単なる別の、既存のフォームということになります。


図19
[プロジェクト] から削除
図20
Unit2 を削除

プロジェクトのソースが次のようになります。
これは、フォームが 1 つだけのプロジェクトの場合と同じです。


リスト15
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


メインフォームの Button1 の OnClick イベントを次のように変更して、型名が TForm2 である AForm というフォームのインスタンスをを実行時に生成するようにします。使用するフォームのインスタンスを生成しないと、実行時に例外が発生します。
Unit2 では、TForm2 に Form2 という名前が定義されいますから、上の方で説明したように、AForm という変数を定義しないで、Form2 のインスタンスを生成しても構いません。


リスト16
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  プロジェクトにないフォームの変数やメソッドを使用
//  Form2は、このプロジェクトには含まれていない
//
//  Form2を生成して使用する
//  Form2のButton1のModalResultはmrOkで、DefaultをTrueにしている
//  Form2のButton2のModalResultはmrCancel
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  AForm : TForm2;
begin
  Panel1.Caption := '';
  Panel2.Caption := '';

  AForm := TForm2.Create(Self);
  try
    AForm.Left := Self.Left - 50;
    AForm.Top  := Self.Top  + 70;

    if AForm.ShowModal = mrOk then begin
      AForm.PutTextString;
      Panel1.Caption := AForm.TestStr;

      Panel2.Caption := AForm.Panel1.Caption;
    end;
  finally
    FreeAndNil(AForm);
  end;
end;

end.


実行結果は次のようになります。
このサンプルでは、{OK] ボタンのクリックと同じ動作を {Enter] キー押下で行うことができます。また、[Esc] キーは [キャンセル] ボタンのクリックと同じ動作となります。
この機能を実現するために、ModalResult プロパティを使用しました。


図21
EXE を起動して Button1 をクリック
  • 表示した Form2 は [Enter] または [Esc] で閉じることもできる
図22
Form2 を [OK] で閉じた場合
  • [Enter] キーで閉じた場合も同じ

図23
Form2 を [キャンセル] で閉じた場合
  • [Esc] キーで閉じた場合も同じ

別の、既存のフォームをプロジェクトに追加することもできます。
[プロジェクト] [プロジェクトに追加] で既存のフォームのユニットファイルを選択します。

プロジェクトにはない既存のフォームをプロジェクトに追加すると、そのフォームは自動生成となります。また、最初の方のテストで確認したように、プロジェクトのソースコードに、そのユニットのパス名が記述されます。ということは、その場所に、そのフォーム関係のファイルがないと正常に動作いないことになります。

複数のプロジェクトで共通して使用するフォームは、その関係ファイルをライブラリのパスが通ったフォルダに置いておくと便利です。このことは、一般的なユニットファイルや、コンポーネント関係のファイルでも同じです。





15_他のユニットは uses 部に追加して使用する


以上のように、プロジェクトに含まれていないユニットも、uses 部に追加すると利用できますが、プロジェクトに含まれていないユニットは、プロジェクトマネージャーには表示されません。

このサンプルでは Unit2 ですが、Unit2.pas を表示したい場合は、uses 部の Unit2 にマウスカーソルを移動し、[Ctrl] キー を押したまま、マウスの左ボタンをクリックするか、[Ctrl] キーを押したまま [Enter] キーを押下します。
もし開かない時は、プロジェクトを再構築またはビルドしてみてください。

pas ファイルで提供されているコンポーネントやユーティリティ類も、uses 部に該当するユニット名を追加すれば利用可能となります。
ユニット名とは、ファイル名から、拡張子 .pas を除いたものです。

コンポーネントやユーティリティ類は複数のファイルで提供されていることがありますが、uses に追加するのは必ずしも、それらの全てが必要とは限りません。普通はそれらに添付している文書に書いてあります。
ただし、使用するユニットは、ライブラリのパスが通っていないと使用できません。



図24
Unit2 は表示されない
  • [プロジェクトに追加] していないユニットは表示されない
  • uses 部に追加すれば [プロジェクトに追加] していないユニットも使用できる
図25
Unit2.pas を開く
  • [Ctrl] + マウス左ボタンクリック
  • または [Ctrl] + [Enter]
図26
メニューからUnit2.pas を開く
  • ポップアップメニューからも開ける

新規にプロジェクトを作成すると、以下のように、interface 部のすぐ下の uses 部にいろいろな ユニット名があります。
つまり、メインフォームだけのプロジェクトでも、これらのユニットの変数 (プロパティ) やメソッドを使用することになるわけです。したがって、プロジェクトに含まれていないフォームでも、そのユニット名を uses 部に追加すれば、同じように利用可能になるわけです。



interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;


フォームの場合は、Form2.Button2 のように、フォーム名の後に . (ドット・ピリオド) を付けて、メソッド、あるいは変数等にアクセスします。他のユニット名の場合は、特にユニット名を指定しなくても、uses 部にあるユニットファイルの中から自動的に探し出してくれます。もちろん、明示的にユニット名を付けても構いません。

プロジェクトに追加しないで使用するフォームは、実行時に生成して使用します。プロジェクトに含まれていませんから、[自動生成] の設定はできません。
コンポーネントも同じです。フォームに配置したコンポーネントは、自動で、そのインスタンス (実体) を生成しますが、uses にそのコンポーネントのユニット名を追加しただけの場合は、実行時に生成して使用することになります。





16_uses 部にないユニットの機能は使用できない


ここまでで説明したように、他のフォームも、フォームを使用しないユニットでも、そのユニット名を uses 部に追加すれば、そのユニット内の関数、メソッド、変数、コンポーネント類等にアクセスできることが分かりました。

逆に、uses 部にないユニット内の関数や変数にはアクセスできません。使用しようとすると、未定義の識別子等のエラーが発生します。ただし例外があります。System.pas 内の関数や変数、定数等は、uses 部に System がなくてもアクセスできるようになっています。

コンポーネントの場合は、フォームにコンポーネントを配置すると、uses 部に、自動的に必要なユニット名が追加される仕組みになっています。



[備考]
Delphi に実装されている定数や変数、関類類がどのユニットに定義されているかは、ヘルプで調べることができます。





17_他のプロジェクトでも使用可能な定数や変数、関数等の定義


ユニットはフォームやコンポーネントのユニットだけではありません。
関数 (function, procedure) や定数等だけを記述したユニットを作成することもあります。uses 部にそのユニット名を追加することにより、他のプロジェクトでも利用できます。
例えば [ファイル] [新規作成] [ユニット Delphi] でユニットを新規作成して、HogeHoge.pas という名前で保存します。そして、コードを以下のようにしたとします。



リスト17
定数や変数、関数等だけを定義したユニット
グローバルな定数や変数、関数として使用
unit HogeHoge;

interface

uses
  Windows;

const
  hgHogeConst = 300;

var
  hgHogeValue : string;

// 関数の宣言部(interface部)
function AddValeu(A: Integer; B: Integer): Integer;

implementation

// 関数の実装部(implementation部)
function AddValeu(A: Integer; B: Integer): Integer;
begin
  Result := A + B;
end;

end.


上のユニットを使用する例です。uses にユニット名の HogeHoge を追加します。
ユニット HogeHoge 内の定数、変数、関数類はクラスのメンバとしての実装していません。したがってこれらはグローバルな定数、変数、関数類として使用できます。グローバルな定数、変数、関数類はユニット名を省略して使用できます。
ただし、他のユニットで定義している変数類の名前と重複しないように注意が必要です。



リスト18
上のユニットを使用するプログラムの例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

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

var
  Form1: TForm1;

implementation

uses
  HogeHoge;

{$R *.dfm}

//=============================================================================
//  ユニットHogeHoge.pasの定数、変数、関数類を使用する例
//
//  usesにユニット名のHogeHogeが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue : Integer;
  LText  : string;
begin
  HogeHoge.hgHogeValue := 'ABC';
  LValue := HogeHoge.AddValeu(200, 300);

  LText := IntToStr(HogeHoge.hgHogeConst) + sLineBreak
         + HogeHoge.hgHogeValue + sLineBreak
         + IntToStr(LValue);

  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

//=============================================================================
//  ユニットHogeHoge.pasの定数、変数、関数類を使用する例
//  グローバルな定数、変数、関数名はユニット名を省略できる
//
//  usesにユニット名のHogeHogeが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LValue : Integer;
  LText  : string;
begin
  hgHogeValue := 'ABC';
  LValue := AddValeu(200, 300);

  LText := IntToStr(hgHogeConst) + sLineBreak
         + hgHogeValue + sLineBreak
         + IntToStr(LValue);

  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.


図27
上のコードの実行結果

[備考]
Delphi が提供しているユニット類も、他のプロジェクトで使用可能なユニットです。ただし、本項のユニットの使用例と同じように、uses 部にユニット名を追加しなければそのユニット内の機能は利用できません。
また、既に述べたように、そのユニットのファイル (拡張子は .pas)、または、そのユニットのコンパイル済ファイル (拡張子は .dcu) のどちらかがライブラリのパスが通った場所にないと利用できません。
uses というのは「使う」と言う意味です、つまり「使う」指定をしなければ使えません。