Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年08月26日] [更新 2022年10月10日] Delphi 一般・その他
TFrame ( フレーム ) の使用
動作確認等 Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 1170]







TFrame ( フレーム ) の使用


Delphi の TFrame コントロールは、複数のコントロールを配置することができるコンテナ コントロールです。フレームという呼び方が一般的に使われています。

TPanel も複数のコントロールを配置できるコンテナですが、継承クラスのユニットとしては保存できません。TFrame は、その継承クラスをユニットとして作成するので、ファイルとして保存して、そのファイルを他のプロジェクトからも利用できます。フォームに配置してしまえば、複数のコンポーネントを内蔵したコンポーネントとして、1 つの部品として使用できます。GUI 操作を伴う定型処理をフレームにしておけば使い回しが可能になります。
フレームに配置したコントロール類の設定内容は、フォームと同じように、拡張子が .dfm のファイルに保存されます。

通常のコンポーネントとの違いの 1 つは、フォームに配置するまでの手順です。コーディングそのものは、TFrame だから言って特別なことはありません。TFrame も単なるクラスです。






01_TFrame の継承クラス TTestFrame の作成と保存


新規にフレームを作成する手順です。既存のプロジェクトを IDE で開いている状態の場合、新規に作成したフレームは、そのプロジェクトに自動的に追加されます。
フレームを作成するということは、実際にはTFrame の継承クラスを作成することです。

IDE の [ファイル] メニューから [新規作成] [その他] を選択して [新規作成] ダイアログを起動します。そのダイアログで左ペインの [Delphi ファイル] を選択して、右ペインの [VC+ フレーム] を選択します。



図1
フレームの新規作成
  • [ファイル] [新規作成] で
    [その他] を選択
  • 左ペインの [Delphi ファイル] 選択
  • [VCL フレーム] を選択

下図のようなフレームが作成されます。ここに必要なコントロール類を配置します。下図では、TFrame のオブジェクト インスペクタで ParentBackground の値を False にして、Color プロパティで背景色を変更しています。



図2
新規作成したばかりのフレーム
図3
コントロール類を配置して背景色を変更

コントロールを配置したら保存しておくことにします。保存する前に、フレームの名前を変更しておきます。今回は、TestFrame という名前にしました。
Form1 という名前のフォームのクラス名は TForm1 です。TestFrame という名前のクラス名は TTestFrame というこになります。



図4
保存する前にフレームに名前を付ける
  • 今回は TestFrame とした
  • クラス名は TTestFrame となる

[ファイル] [名前を付けて保存 ...] で保存のダイアログを起動して保存します。今回は TestFrameUnit.pas の名前で保存してテストしています。
この保存先には、ライブラリパスが通っている必要はありません。


図5
保存したフレーム関係のファイル
  • 今回の保存先は D:\000\Delph_Frames
  • ここにはライブラリパスは通っていなくてもよい

保存したファイルは、クラスのユニットファイルです。クラスが TFrame だからと言って特別なことはありません。TFrame も単なるクラスです。フレームの作成方法としては、これで終了ですが、処理コードが全くないのでは寂しいので、今回は以下のようなコードを実装しました。



リスト1
unit TestFrameUnit;

interface

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

type
  TTestFrame = class(TFrame)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FText : string;
    procedure SetFText(const Value: string);
  public
    { Public 宣言 }
    procedure ShowDialog;
    property Text : string Read FText write SetFText;
  end;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  Text プロパティの書き込み用メソッド
//  循環参照を回避するために、クラス内のグローバル変数に代入する
//-----------------------------------------------------------------------------
procedure TTestFrame.SetFText(const Value: string);
begin
  if FText <> Value then begin
    FText := Value;

    // Textプロパティの値が変わったら Edit.Text の値も変更
    Edit1.Text := FText;
  end;
end;

//=============================================================================
//  Edit1.Text に、処理した結果としての文字列を代入
//=============================================================================
procedure TTestFrame.Button1Click(Sender: TObject);
var
  LValue : string;
begin
  if FText.IsEmpty then begin
    LValue := ' 日本語';
  end else begin
    LValue := FText + ' 日本語';
  end;
  Edit1.Text := LValue;
end;

//-----------------------------------------------------------------------------
//  ShowDialog メソッド
//  Text プロパティの値を表示
//-----------------------------------------------------------------------------
procedure TTestFrame.ShowDialog;
var
  LText : string;
begin
  LText := 'Text プロパティの値' + sLineBreak + sLineBreak + FText;
  MessageBox(Handle, PChar(LText), 'プロパティの値', MB_ICONINFORMATION);
end;

end.


このサンプルでは、string 型のプロパティ Text を追加実装しています。TStringList や TBitmap など、非ビジュアルなオブジェクト型のプロパティの実装方法については、以下の記事のサンプルを参考にしてください。TPicture 型のプロパティを実装しているサンプルです。



コンポーネントには汎用性が必要です。フレームも同じです。コンポーネントのソースコードに特定の名前のコントロールにアクセスするコードがあると、そのコンポーネントは、その名前のコントロール類を配置しているフォーム専用になってしまいます。Delphi に実装されている VCL のコンポーネント類 (コントロール類) にも、そのような処理は一切ありません。



[備考]
上のサンプルには、TTestFrame に Text プロパティを追加しています。継承元の TFrame には Text プロパティはありません。
もし、継承元のクラスにあるプロパティと同じ名前のプロパティを実装すると、元のプロパティの機能が置き換わり、新たに実装したプロパティが有効になります。
メソッドゃメッセージ処理はオーバライドできます。inherited 指令で継承元のコードを実行することができます。




02_作成した TTestFrame を現在のプロジェクトで使用


既存のプロジェクトを IDE で開いている状態の場合、新規に作成したフレームは、そのプロジェクトに自動的に追加されます。
この時、プロジェクトのソースコードは、以下のようになります。赤の下線部分が、フレームのユニットの保存のパス名となっています。


リスト2
program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  TestFrameUnit in '..\..\Delphi_Frames\TestFrameUnit.pas' {TestFrame: TFrame};

{$R *.res}

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


フレームのコードが表示されている場合、フレームのコードを編集する必要がなければ、[Ctrl] + [F4], あるいは、ポップアップメニューの [このページを閉じる] で閉じます。

フォームデザイナの状態、つまり、フォームが表示されている状態にします。コードエディタとフォームデザイナは [F12] キーで切り替わります。
コンポーネントパレットの [Standard] タブの左端の [Frames] のアイコンがあります。このアイコンをクリックしてからフォームをクリックします。



図6
[Frames] のアイコンをクリック
  • [Standard] タブの左端にある
  • フォームデザイナにしておく
  • [Frames] をクリックしたら
    フォームをクリック

[Frames] のアイコンをクリックしてからフォームをクリックすると、下図のダイアログが現れます。これは利用可能なフレームの一覧で、プロジェクトに追加したフレームのリストです。利用するフレームを選択します。
このダイアログは、[Frames] のアイコンのダブルクリックでも起動します。



図7
プロジェクトに追加したフレームのリスト
  • 使用するフレームを選択
  • [OK] ボタンをクリック

ダイアログからフレームを選択すると、そのフレームがフォームに配置されます。



図8
フォームに追加したフレーム
  • 背景に色があるのがフレーム
  • 位置やサイズは自由に変更可能

上のプロジェクトのソースでは、ユニットのバスが相対パスになっています。以下のように、絶対パスにしてしまえば、プロジェクトを他のフォルダに移動しても、このフレームが使用できます。

program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  TestFrameUnit in 'D:\000\Delphi_Frames\TestFrameUnit.pas' {TestFrame: TFrame};


[備考 1]
複数のフォームを使用するプロジェクトで、メインフォーム以外のフォームにフレームを配置する手順も全く同じです。動的に生成して表示するフォームでも同じです。フォームを動的に生成する場合、フレームを動的に生成する必要はありません。
TButton, TPanel 等を配置して使用するフォームを動的に生成する場合、TButton, TPanel 等を動的に生成する必要はありません。それと同じです。

[備考 2]
フレームの本体は、現在のプロジェクトに追加したユニットですから、[プロジェクトマネージャ] でユニット名を変更できます。
ただし、当然ながら、そのフレームを使用している他のプロジェクトにも影響を及ぼします。そのフレームを使用しているプロジェクトでは、プロジェクトを開いた後、古い名前のフレームを [プロジェクト] [プロジェクトから削除] で削除しなければなりません。
このことは、ユニットに対して、一般的に言えることです。




03_作成した TTestFrame を使用するコードを書いて実行


フレームを配置しただけのサンプルでは寂しいので、今回は、図の下のコードを実装してテストしています。下図はその実行結果の例です。
このサンプルは、フレームの Edit1 の Text プロパティ (TEdit.Text) の値が変更されると、フォームの Edit1 の Text プロパティ (TEdit.Text) の値が自動的に更新するようにしています。



図9
[Button1] をクリック
  • フレームの Edit.Text に文字列を代入
図10
[Button2] をクリック
  • フレームの Text プロパティに文字列を代入して、ShowDialog メソッドを実行
図11
フレームの [Button1] をクリック
  • フレーム内部の処理結果としての文字列

テストに使用したコードです。uses 部の TestFrameUnit は、コーディングを開始するか、このユニットを保存すると自動的に追加されます。

TestFrame1 の Button1 クリックイベントに以下のコードがあります。このコードは、イベントを作成する時に、TTestFrame の Button1 に OnClick イベントが実装されていない場合は生成されません。
TTestFrame の Button1 の OnClick イベント実装の有無に関係なく、このコードがあってもエラーにはなりませ。これは、inherited 指令でも同じです。


TestFrame1.Button1Click(Sender);


リスト3
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  TestFrame1 上の Edit1 ( TEdit ) に文字列をセット
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  TestFrame1.Edit1.Text := 'テスト文字列';
end;

//=============================================================================
//  (1) TestFrame1 の Text プロパティに文字列を代入
//  (2) TestFrame1 の ShowDialog メソッドを実行
//      ShowDialog メソッドは Text プロパティの値を表示するメソッド
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  TestFrame1.Text := '意味不明な文章';
  TestFrame1.ShowDialog;
end;

//=============================================================================
//  TestFrame1 の Button1 クリックイベント
//  TestFrame1 の元の Button1 クリックイベントのコードを実行
//  一般のコンポーネント (クラス) における、継承元のコードの実行に相当する機能
//
//  TestFrame1.Button1Click(Sender); は、
//  フレーム内部で処理した文字列をフレームの Edit1 に表示
//=============================================================================
procedure TForm1.TestFrame1Button1Click(Sender: TObject);
begin
  TestFrame1.Button1Click(Sender);
end;

//=============================================================================
//  TestFrame1 の Edit1 の OnChange イベント
//=============================================================================
procedure TForm1.TestFrame1Edit1Change(Sender: TObject);
begin
  // このフォームの Edit1.Text の値を TestFrame1 の Edit1.Text の値と同じにする
  Edit1.Text := TestFrame1.Edit1.Text;

  // または
  // Edit1.Text := TEdit(Sender).Text;
end;

end.


コンポーネント内の変数やデータへのアクセスは、プロパティやメソッド、あるいはイベントを実装して処理しますが、メッセージも利用できます。文字列、数値、レコード型のデータ、画像等については以下を参考にしください。



以下は、上のリンクの記事と同じ WM_COPYDATA メッセージを使用して、文字列を、親コントロールであるフォームに送信する例です。
WM_COPYDATA メッセージを使用すれば、メッセージが、どのコントロールから送られてきたかを FindControl 関数で知ることができます。



図12
フレームからのデータを受信

フレームのクラスに実装する送信側のコードです。
このコードでは、データの送信先を親フォームにしていますが、送信先のウィンドウハンドルをプロパティとして実装しておけば、任意のフォームに送信することができます。



リスト4
//=============================================================================
//  Button1 クリックの処理結果としての文字列を親コントロールに送信
//=============================================================================
procedure TTestFrame.Button1Click(Sender: TObject);
var
  LValue    : string;
  LCopyData : TCopyDataStruct;
begin
  LValue := '処理結果のデータ';

  LCopyData.dwData := 1234;
  LCopyData.cbData := (Length(LValue) + 1) * SizeOf(Char);
  LCopyData.lpData := Pointer(LValue);

  SendMessage(Parent.Handle, WM_COPYDATA, Handle, LPARAM(@LCopyData));
end;


メッセージの受信側である、フレームを配置しているフォームのコートです。WM_COPYDATA メッセージ受信専用のメソッドを実装します。このメソッドを実装していると、フレームが WM_COPYDATA メッセージを送信した時に、このメソッドが自動的に実行されます。つまり、通知メッセージと同じように、イベント同様の機能として動作します。



リスト5
//----------------------------------------------------------------------------
//  データの受信処理
//  宣言部は以下
//  procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
//----------------------------------------------------------------------------
procedure TForm1.WMCopyData(var Message: TWMCopyData);
var
  LText : string;
begin
  if Message.CopyDataStruct.dwData = 1234 then begin
    LText := PChar(Message.CopyDataStruct.lpData);
    LText := FindControl(Message.From).Name + ' からの受信文字列' + sLineBreak + LText;

    MessageBox(Handle, PChar(LText), 'データ受信', MB_ICONINFORMATION);
  end;
end;


この WM_COPYDATA メッセージによる方法は、この例の場合、実用性はあまりありません。フレームの Button1 をクリックして、何かの処理をして終了したらその結果をフォーム側で受け取るには、本項のサンプルのように、フォームに配置したフレームの、Button1 のクリックイベントを使用すれば可能です。処理結果としてのデータは、プロパティなどで取得できます。

フレームのクラスに、処理用のメソッドを実装しておきます。フォームに配置したフレームの、Button1 のクリックでそのメソッドを実行して結果を取得する方法もあります。プロパティ取得用のメソッドを実装して、そのプロパティを取得する際に処理する方法もあります。
これらは、コンポーネントやクラスの実装で一般的に行われている方法です。



//=============================================================================
//  フォームに配置した TestFrame1 の Button1 のクリックイベント
//=============================================================================
procedure TForm1.TestFrame1Button1Click(Sender: TObject);
var
  LValue : Integer;
begin
  // GetDataFunc は、フレームのクラスに実装した Integer 型の値を返す関数
  LValue := TestFrame1.GetDataFunc;
end;

//=============================================================================
//  フォームに配置した TestFrame1 の Button2 のクリックイベント
//=============================================================================
procedure TForm1.TestFrame1Button2Click(Sender: TObject);
var
  LValue : Integer;
begin
  // GetDataProc は、フレームのクラスに実装した手続き
  // LValue を var 渡しにしておけば処理結果の取得が可能
  TestFrame1.GetDataProc(LValue);
end;


[備考]
フレームの処理コード等、フレームの構成を変更した時は、[Shift] + [F9] で再構築 (ビルド) するようにします。そうしないと変更内容が反映されないことがあります。
これは、他のフォームやユニット類を使用する際に一般的に言えることです。




04_作成した TTestFrame を別のプロジェクトで利用


作成して保存したフレームを別のプロジェクトで利用する手順です。
新規に VCL アプリケーションの作成状態にするか、既存のプロジェクトを開きます。

[プロジェクト] [プロジェクトに追加] で、保存しているフレームのユニットを開きます。
フレームのコードが表示されたら、[Ctrl] + [F4], あるいは、ポップアップメニューの [このページを閉じる] で閉じます。

追加したユニットは、プロジェクトのソースコードに以下のように追加されます。赤の下線部分は、フレームのユニットファイルのバス名です。
[ファイル] [開く] でフレームのファイルを開いても、現在のプロジェクトには追加されません。この場合は、単なるフレームの編集状態となります。


リスト6
program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  TestFrameUnit in '..\..\Delphi_Frames\TestFrameUnit.pas' {TestFrame: TFrame};

{$R *.res}

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


フォームデザイナの状態、つまり、フォームが表示されている状態にします。コードエディタとフォームデザイナは [F12] キーで切り替わります。
コンポーネントパレットの [Standard] タブの左端の [Frames] のアイコンがあります。このアイコンをクリックしてからフォームをクリックします。



図13
[Frames] のアイコンをクリック
  • [Standard] タブの左端にある
  • フォームデザイナにしておく
  • [Frames] をクリックしたら
    フォームをクリック

[Frames] のアイコンをクリックしてからフォームをクリックすると、下図のダイアログが現れます。これは利用可能なフレームの一覧で、プロジェクトに追加したフレームのリストです。利用するフレームを選択します。
このダイアログは、[Frames] のアイコンのダブルクリックでも起動します。


図14
プロジェクトに追加したフレームのリスト
  • 使用するフレームを選択
  • [OK] ボタンをクリック

ダイアログからフレームを選択すると、そのフレームがフォームに配置されます。



図15
フォームに追加したフレーム
  • 背景に色があるのがフレーム
  • 位置やサイズは自由に変更可能
  • こフレームの名前は TestFrame1
  • 名前はオブジェクトインスペクタで変更可能

[備考]
プロジェクトを別の場所に移動しても、プロジェクトのソースコードのフレームのユニットのパス名は自動では変わりません。「ファイルが見つかりません」等のエラーが発生した場合は、手作業でパス名を変更するか、[プロジェクト] [プロジェクトから削除] でフレームを削除して、再度フレームをプロジェクトに追加します。




05_作成した TTestFrame を実行時に生成して使用


通常のコンポーネントと同じように、フレームも実行時に生成して使用することもできます。
ここでは、既に作成して保存したクラス名が TTestFrame のフレームを実行時に生成します。

[プロジェクト] [プロジェクトに追加] で、保存しているフレームのユニットを開きます。
フレームのコードが表示されたら、[Ctrl] + [F4], あるいは、ポップアップメニューの [このページを閉じる] で閉じます。

追加したユニットは、プロジェクトのソースコードに以下のように追加されます。赤の下線部分は、フレームのユニットファイルのバス名です。
[ファイル] [開く] でフレームのファイルを開いても、現在のプロジェクトには追加されません。この場合は、単なるフレームの編集状態となります。


リスト7
program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  TestFrameUnit in '..\..\Delphi_Frames\TestFrameUnit.pas' {TestFrame: TFrame};

{$R *.res}

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


フォームのユニットの uses 部に、使用するフレームのユニット名 TestFrameUnit を追加します。フレームをフォームに配置して使用する場合と違って、自動的には追加されません。
今回作成して保存し たフレームのクラス名は TTestFrame になっています。この型の変数を宣言して、実行時にそのインスタンスを Create メソッドで生成します。以下のコードでは、public 部の赤の下線の FFrame がその変数です。



図16
設計時のフォームの画面
図17
[F9] でコンパイルして実行した結果

リスト8
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用のフレームを生成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFrame := TTestFrame.Create(Self);

  FFrame.Parent := Self;
  FFrame.Left := Button1.Left;
  FFrame.Top := 70;
  FFrame.ParentBackground := False;
  FFrame.Color := clWindowFrame;
end;

end.


上の方のサンプルと同じ動作をするサンプルにしてみます。
上の方のサンプルでは、フレームの名前が TestFram1 でしたが、これを FFrame に変更します。

コンポーネントのインスタスを実行時に生成する時は、イベントの作成にオブジェクトインスペクタが使用できません。イベントは、メソッドを作成して、コンポーネントを生成する時に、そのメソッドをイベントに割り当てます。以下のコードの赤の下線部分がそれに相当します。



リスト9
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure FrameButton1OnClick(Sender: TObject);
    procedure FrameEdit1OnChange(Sender: TObject);
  public
    { Public 宣言 }
    FFrame : TTestFrame;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用のフレームを生成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFrame := TTestFrame.Create(Self);

  FFrame.Parent := Self;
  FFrame.Left := Button1.Left;
  FFrame.Top := 70;
  FFrame.ParentBackground := False;
  FFrame.Color := clWindowFrame;

  // FFrame 上のコントロール類のイベント用
  FFrame.Button1.OnClick := FrameButton1OnClick;
  FFrame.Edit1.OnChange  := FrameEdit1OnChange;
end;

//=============================================================================
//  FFFrame 上の Edit1 ( TEdit ) に文字列をセット
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FFrame.Edit1.Text := 'テスト文字列';
end;

//=============================================================================
//  (1) FFrame の Text プロパティに文字列を代入
//  (2) FFFrame の ShowDialog メソッドを実行
//     ShowDialog メソッドは Text プロパティの値を表示するメソッド
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  FFrame.Text := '意味不明な文章';
  FFrame.ShowDialog;
end;

//-----------------------------------------------------------------------------
//  FFrame の Button1 クリックイベント用のメソッド
//  FFrame の元の Button1 クリックイベントのコードを実行
//  一般のコンポーネント (クラス) における、継承元のコードの実行に相当する機能
//
//  FFrame.Button1Click(Sender); は、
//  フレーム内部で処理した文字列をフレームの Edit1 に表示
//-----------------------------------------------------------------------------
procedure TForm1.FrameButton1OnClick(Sender: TObject);
begin
  FFrame.Button1Click(Sender);
end;

//-----------------------------------------------------------------------------
//  FFrame の Edit1 の OnChange イベント用のメソッド
//-----------------------------------------------------------------------------
procedure TForm1.FrameEdit1OnChange(Sender: TObject);
begin
  // このフォームの Edit1.Text の値を FFrame の Edit1.Text の値と同じにする
  Edit1.Text := FFrame.Edit1.Text;

  // または
  // Edit1.Text := TEdit(Sender).Text;
end;

end.


コンポーネント (クラス) を続行時に生成して使用する場合は、上のサンプルのように、そのコンポーネント (クラス) の型の変数を宣言して、その変数のインスタンスを生成します。この変数名が、実行時生成のインスタンス名で、Name プロパティの値に相当します。

フォームだけは特別です。あるクラス名のフォームを、実行時に 1 つしか生成しないのであれば、変数の宣言は特に必要ありません。クラス名のプリフィックス T を除いた変数のインスタンスを生成できます。例えば TForm2 の場合は、Form2 のインスタンスを生成して使用できます。この変数名は、TForm2 の Name プロパティと同じです。
フォームのこの変数は、ユニット内のグローバル変数として宣言されていまます。



[備考 1]
プロジェクトを別の場所に移動しても、プロジェクトのソースコードのフレームのユニットのパス名は自動では変わりません。「ファイルが見つかりません」等のエラーが発生した場合は、手作業でパス名を変更するか、[プロジェクト] [プロジェクトから削除] でフレームを削除して、再度フレームをプロジェクトに追加します。

[備考 2]
フレームを実行時に生成する場合、フレームのユニットファイルか、ライブラリパスの通った場所にあれば、フレームをプロジェクトに追加する必要はありません。フォームの uses 部に、フレームのユニット名を追加すれば、実行時に生成できます。

フレーム以外の他のユニットも同じです。ライブラリパスが通った場所にあるユニットは、プロジェクトに追加しなくても、uses 部にユニット名を追加すれば利用できます。








これ以降の記事は、フレームの作成と使用方法とは直接静関係ありません。

フレームの作成と使用方法の記事執筆中に、あるコミュニティの場で、フレームについての書き込みがありました。そこで、サンプルなども追加したのが本ページの記事です。そのコミュニティにおける書き込みに、理解が不十分なまま使用されていると思われる用語やコードがありました。
これ以降は、それらの用語やコードに関する記事となっています。



06_関数と手続きとメソッドとインスタンス


メソッドは、クラスのメンバーの関数や手続きのことです。以下のコードの関数 TestFunc と TestProc 手続きは、クラスのメンバーにはなっていませんから、メソッドではありません。もちろん、イベント (の手続き、メソッド) でもありません。


リスト10
unit Unit2;

interface

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

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

  function TestFunc: Integer;
  procedure TestProc;

var
  Form2: TForm2;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  この関数は、クラスのメンバーではないので、メソッドでもイベントでもない
//-----------------------------------------------------------------------------
 function TestFunc: Integer;
 begin
   Result := 123456;
 end;

//-----------------------------------------------------------------------------
//  この手続きは、クラスのメンバーではないので、メソッドでもイベントでもない
//-----------------------------------------------------------------------------
procedure TestProc;
begin
  // 何かの処理
end;

end.


上のコードの Testfunc, TestProc は、ユニット内のグローバルな関数、手続きです。したがって、他のユニットからは、uses 部に、これらの関数、手続きを宣言しているユニット名を追加すれば使用できます。以下のように、ユニット名を省略しても使用できます。
これは、Delphi が提供している多くの関数や手続きが、その関数や手続きが実装されているユニット名を省略できるのと同じです。


リスト11
uses
  Unit2;

procedure TForm1.Button1Click(Sender: TObject);
var
  LInt : Integer;
  LStr : string;
begin
  LInt := testFunc;

  // ユニット名を省略しない場合
  LInt := Unit2.testfunc;

  // System.SysUtils.IntToStr(LInt) としなくても動作する
  LStr := IntToStr(LInt);
end;


Testfunc 関数は、Unit2 というユニットに実装しています。このユニットには TForm2 というクラスが実装されいます。TForm2 のインスタンス名は Form2 です。
同じユニット内にあるからといっても、関数 Testfunc はクラス Tform2 のメンバーではないので、他のユニットからは、以下のようには使用できません。「未定義の識別子」のエラーになります。

未定義の識別子のエラーは、IDE のコードエディタの入力支援機能によって、多くの場合、コーディングの段階で回避することができます。



uses
  Unit2;

procedure TForm1.Button1Click(Sender: TObject);
var
  LInt  : Integer;
  LText : string;
begin
   // TestFunc は TForm2 のメンバではない
   // TForm2 の継承元にも TestFunc というメソッドは存在しない
   // コンパイル時に TestFunc は「未定義の識別子」のエラーとなる
   LInt := Form2.TestFunc;

  // Caption は TForm2 にはないが、継承元のクラスにあるのでエラーにならない
  LText := Form2.Caption;
end;


一般的に、クラスで使用する関数や手続きを、上のように宣言して使用することはありません。クラスで使用する関数や手続きは、メソッドとして実装するのが一般的です。

上の Testfunc 関数と TestProc 手続きをクラスのメンバーとしてメソッドにすると、例えば以下のようになります。クラスのメンバーであるメソッドの実装部 ( implementation 部 ) の関数名の部分には、必ずクラス名が付きます。

メソッドを、他のユニットからも使用可能にするには、そのメソッドを public 部で宣言します。クラス内部だけで使用するメソッドは、private 部で宣言します。private 部で宣言したメソッドは、他のユニットからは使用できません。



リスト12
unit Unit2;

interface

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

type
  TForm2 = class(TForm)
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    function TestFunc: Integer;
    procedure TestProc;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  この関数は、クラスのメンバーなのでメソッド
//  関数名の前のクラス名は、このクラスのメソッドであることを意味しいる
//-----------------------------------------------------------------------------
function TForm2.TestFunc: Integer;
begin
   Result := 123456;
end;

//-----------------------------------------------------------------------------
//  この手続きは、クラスのメンバーなのでメソッド
//  手続き名の前のクラス名は、このクラスのメソッドであることを意味しいる
//-----------------------------------------------------------------------------
procedure TForm2.TestProc;
begin
  // 何かの処理
end;


クラスのメンバーであるプロパティやメソッドを他のユニットで使用するには、以下のように、そのクラスのインスタンス (実体) を使用します。
フォームに TButton コンポーネントを配置すると、Button1 という名前の TButton が配置されます。更にもう 1 つ配置すると、末尾が 2 の Button2 となります。この時の Button1, Button2 が TButton コンポーネントのインスタス (実体) です。これを文字列で表現するのが Name プロパティです。コンポーネントのインスタンス名の変更は、必ずオブジェクトインスペクタで行います。Name プロパティの値を変更します。

今回作成したフレームも同じです。クラス名が TTestFrame のフレーム、つまり、クラス名が TTestFrame というコンポーネント (コントロール) をフォームに配置すると、Name プロパティの値が TestFrame1 というフレームになります。



uses
  Unit2;

procedure TForm1.Button1Click(Sender: TObject);
var
  LInt  : Integer;
  LText : string;
begin
   // From2 は、クラス名が TForm2 のフォームのインスタンス
   LInt := Form2.TestFunc;

   // Button1 は、TButton コンポーネントのインスタンス
   LText := Button1.Caption;
end;


[備考]
コンポーネントなどのクラスを実行時に生成するには、そのクラスの型の変数を宣言して、その変数のインスタンスを生成します。この変数名がインスタス名です。この場合のインスタンス名は、Name プロパティの値と同じではありません。




07_クラスのインスタンスとクラス型の変数宣言


フォームにコントロールを配置すると、それらのコントロールのインスタンスは自動的に生成されます。以下のサンプルでは、Button1, Button2, Panel1 というインスタンスが生成されます。各々のクラス名は TButton, TPanel です。このクラス名は、それらのインスタンスの「型」です。
このサンプルをコンパイルして作成した EXE を起動して、Button1 をクリックしたのが下図です。Panel1 の背景色が変更されているのが確認できます。



図18
EXE を起動して [Button1] をクリック
  • Panel1 の背景色が変わる

このサンプルでは、FPanel という TPanel 型の変数を宣言しています。Button2 をクリックすると、この FPanel の背景色を変更するコードを実行します。しかし、実行すると例外が発生します。
コントロール (コンポーネント)、クラスなど、その型がクラスのオブジェクトは、その型の変数を宣言しただけでは正常に動作しません。「実行時に生成して使用」のサンプルのように、Creatae メソッドで、その変数のインスタンスを生成する必要があります。

コントロールなど、クラス型の変数のインスタンスを生成するということは、その変数が使用するメモリを確保することです。必要なメモリが確保されていないと、この例のように access violation (アクセス違反) の例外が発生します。



図19
[Button2] をクリック
  • 例外が発生する

上の例外のダイアログは、EXE を直接実行した場合です。設計時に IDE で [F9] でコンパイルして実行した場合は下図の [デバッガの例外通知] ダイアログが現れます。
この [デバッガ例外通知] ダイアログで [継続] をクリックすると、上の図のダイアログが現れます。[ブレーク] をクリックすると、エラーが発生した部分でプログラムが止まります。この場合は [Ctrl] + [F2] でプログラムを終了します。



図20

リスト13
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;  // Button1 は、フォームに配置した TButton のインスタンスButton2: TButton;  // Button2 は、フォームに配置した TButton のインスタンス名
    Panel1: TPanel;    // Panel1 は、フォームに配置した TPanel のインスタンス名
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FPanel : TPanel;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Panel1 の Color プロパティの値が反映できるようにする
  Panel1.ParentBackground := False;
end;

//=============================================================================
//  Panel1 の Color プロパティの値を設定
//  Panel1 は、TPanel のインスタンス
//  フォームに配置したコントロールは自動的にインスタンスが生成される
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Panel1.Color := clGreen;
end;

//=============================================================================
//  FPanel の Color プロパティの値を設定
//  FPanel は、TPanel 型の変数
//  変数を宣言しただけではインスタンスは生成されない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  FPanel.Color := clRed;
end;


この例外の発生は、参考リンクの記事にあるように、次のようにして回避できます。



procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(FPanel) then begin
    FPanel.Color := clRed;
  end;
end;


[備考]
TPanel.Caption や TEdit.Text など、string 型のプロパティの場合は、インスタンスが生成されていなくても、上記の例外は発生しません。これらは、内部で自分自身のインスタンスが生成されているかをチェックして、生成されていない場合は処理をしません。その結果、例外が発生しません。
自分自身のインスタンス生成は、Self の値が nil であるかで判定できます。


変数名には、クラス名や予約語を使用しないようにします。コンパイル時にエラーが発生せず、実行時にもエラーや例外が発生しなくても混乱の原因となります。
以下は、TButton という TPanel 型の変数を宣言して使用した場合です。この TButton は、TPanel のオブジェクトとしては機能します。しかし、Tbutton は、クラスの型としての機能が失われます。
Delphi では、クラス名のプリフィックスとして大文字の T が慣習的に使用されています。変数名にクラス名を使用することを回避するのは比較的容易と思われます。



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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  TButton := TPanel.Create(Self);
  TButton.Parent := Self;
  TButton.SetBounds(30, 50, 150, 60);
  TButton.ParentBackground := False;
end;

//=============================================================================
//  Button1 の Onclick イベント処理
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  TButton.Color := clGreen;

  // TButton は変数として使用しているのでコンパイルエラーとなる
  // この型には指定した演算子は使えません
  if Sender is TButton then begin

  end;
end;




08_イベントの発生とイベントの手続きの実行


コントロールに対して何かしらの操作が行われると、そのコントロールは、操作に応じたメッセージを親コントロールに送信します。プログラムにおけるイベントは、そのメッセージの捕捉機能です。「イベントの作成」は、メッセージの捕捉機能を手続き (メソッド) として実装することを意味します。メッセージを捕捉した時がイベントの発生です。この時、コントロールに対して何かしらの操作が行われたことになります。

フォームに配置したコントロールや、今回のようにフレームに配置したコントロールのイベントは、オブジェクトインスペクタで作成します。
TButton など、OnClick イベントを実装しているコントロール類は、フォームに配置したそのコントロールをダブルクリックすることでも OnClick イベントを作成できます。

オブジェクトインスペクタの入力部分をダブルクリックしてでイベントを作成した場合、イベントの実装部のイベント名の部分は、クラス名、コントロールのインスタンス名、イベント名、という形式になります。以下は、TFrom1 というクラスのフォームに配置した、Button1 というコントロールの OnClick イベントであることを意味しています。



procedure  TForm1.Button1Click(Sender: TObject);
begin
//
end;


上のリンクの記事に、イベントの手続きをメソッドとして実行するサンプルがあります。イベントの手続きを単なる手続きとして実行するわけですが、以下もその例です。
このサンプルでは、フォーム生成時に Button2 の OnClick イベントを無効にしています。したがって、Button2 をクリックしてもダイアログを表示しません。一方、Button1 をクリックすると、Button2 のイベントをメソッドとして実行します。その結果、ダイアログが表示されます。



図21
イベントのメソッドを実行
  • [Button1] をクリックすると
    ダイアログを表示
  • Sender は Button1
  • [Button2] をクリックしても
    ダイアログは表示しない

リスト14
//=============================================================================
//  フォーム生成時の処理
//  Button2 の OnClick イベントを無効にする
//  オブジェクトインスペクタの該当イベントの部分を空にするのと同じ
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Button2.OnClick := nil;
end;

//=============================================================================
//  Button2 の OnClick イベントのコードをメソッドとして実行
//  Button2 の OnClick イベントが発生するわけではない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Button2Click(Sender);
end;

//=============================================================================
//  このイベントは、フォーム生成時に無効にしたので発生しない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LText : string;
begin
  LText := 'Sender : ' + TControl(Sender).Name;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


上のサンプルで、Button2 の OnClick イベントを有効にした場合、イベントが発生した場合と、単にイベントのめメソッドを実行した場合の違いは、フォーカスの移動があるかないかの程度です。

以下は TCheckBox の例です。Button1 をクリックして、CheckBox1 の OnClick イベントのメソッドを実行しても、CheckBox1 のチェック状態が変わることはありません。イベントは、手続きとして実装されていますが、イベントが発生して実行する場合と、単なる手続きとして実行する場合とで、挙動が同じとは限りません。



図22
[Button1] をクリック
  • CheckBox1 の OnClick のメソッドを実行
  • CheckBox1 のチェック状態は変化しない
図23
CheckBox1 をクリック
  • CheckBox1 の OnClick イベント発生
  • CheckBox1 のチェック状態が変わる

リスト15
//=============================================================================
//  CheckBox1 の OnClick イベントのコードをメソッドとして実行
//  CheckBox1 の OnClick イベントが発生するわけではない
//  したがって、CheckBox1 のチェック状態は変化しない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  CheckBox1Click(Sender);
end;

//=============================================================================
//  CheckBox1 の OnClick イベント
//  このイベントが発生する度に CheckBox1 のチェック状態が変わる
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
var
  LText : string;
begin
  LText := 'Sender : ' + TControl(Sender).Name + sLineBreak
         + BoolToStr(CheckBox1.Checked, True);
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


コードで TButton や TCheckBox をクリックする操作を行うには、BM_CLICK メッセージを対象のコントロールに送信します。



  // Button1 をクリックする
  SendMessage(Button1.Handle, BM_CLICK, 0, 0);
  // または
  Button1.Perform(BM_CLICK, 0, 0);

  // CheckBox1 をクリックする
  SendMessage(CheckBox1.Handle, BM_CLICK, 0, 0);
  // または
  CheckBox1.Perform(BM_CLICK, 0, 0);




09_ウェブページに掲載されているコードの利用


当サイトのサンプルコードも含めて、ネット上のウェブページに掲載されているサンプルコードの利用についてのヒントです。

上の TCheckBox を使用するサンプルのコードは、コメント行を削除すると以下のようになっています。このコードを IDE のコードエディタに貼り付けただけでは正常に動作しません。


リスト16
unit Unit1;

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
  TForm1 = class(TForm)
    Button1: TButton;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  CheckBox1Click(Sender);
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
var
  LText : string;
begin
  LText := 'Sender : ' + TControl(Sender).Name + sLineBreak
         + BoolToStr(CheckBox1.Checked, True);
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.


サンプルコードをコードエディタに貼り付ける前に、サンプルで使用しているコントロール類 (コンポーネント類) をフォームに配置する必要があります。
このサンプルで使用しているコントロールは、「特別な領域」の赤の下線部分です。このサンプルでは、TButton と TCheckBox を 1 つづつ使用します。



図24
サンプルで使用しているコンポーネント類
  • Button1, CheckBox1 は
    Name プロパティの値

新規に VCL フォームアプリケーションを作成します。
フォームに TButton と TCheckBox を配置します。



図25
必要なコンポーネントをフォームに配置

オブジェクト・インスペクタで、各コンポーネントの Name プロパティを設定します。今回のサンプルは、デフォルトのまま使用していますから、実際には設定する必要はありません。
Name プロパティの値は、「特別な領域」に記述されています。



図26
コンポーネントの Name プロパティを設定

コードエディタをアクティブして、[Ctrl] + [A] で全て選択して [Delete] で削除します。全て削除できたらサンプルコードを貼り付けます。
今回のサンプルのユニット名は Unit1 です。もし、別のユニット名の場合は、[ファイル] [プロジェクトに名前を付けて保存] でプロジェクトを保存します。その時、ユニット名をそのユニット名で保存します。
次はイベントの設定です。設定が必要なイベント類も、「特別な領域」に記述されています。



図27
使用しているイベント類

オブジェクト・インスペクタで、各コンポーネントのイベントを下図のように設定します。
これで完了です。[F9] でコンパイルして実行できます。



図28
オブジェクト・インスペクタでイベントを設定
  • Button1
    OnClick のリストから Button1Chick を選択
  • CheckBox1
    OnClick のリストから CheckBox1Click を選択