Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年04月10日] [更新 2017年01月09日] Delphi サンプルプログラム集
   
161_複数の同じコンポーネントを実行時に生成
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
161_CreateComponent_List.zip [6,478 KB] 2017年01月09日版(EXE同梱)







複数の同じコンポーネントを実行時に生成


実行時に,同じクラス型のコンポーネントを複数生成して操作する際の参考サシンプルです.
サンプルは TButton や TPanel ですが,それ以外のコンポーネントでも同様の方法で扱えます.





01_フォーム上のコンポーネントのリスト


本ページではコンポーネントのリストを扱っていますが,フォームに配置したコンポーネントは以下のコードで列挙できます.ClassName でクラス名が取得でき,ClassNameIs でクラス名の確認もできますから,型キャストして,各々のコンポーネントの情報が取得できます.



リスト1
フォーム上のコンポーネントのリスト
//=============================================================================
//  Form1(Self)上に配置しているコンポーネントの列挙
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LCompo : TComponent;
  i      : Integer;
begin
  Memo1.Lines.Clear;

  for i := 0 to Self.ComponentCount - 1 do begin
    LCompo := Self.Components[i];
    Memo1.Lines.Add(LCompo.Name);
  end;
end;




02_複数のパネルを作成 ( 配列を使用 )


任意の要素を追加したり削除する等の操作には,一般的にリストを使用しますが,ネット上に配列を試使用するンプルがありましたので,ここでも配列を使用したサシンプルを掲載しておきます.
配列には動的配列と静的配列があります.動的配列を使用したとしても,最終的には配列の要素数を決定しなければなりません.

本サシンプルでは TPanel を実行時に生成します.TPanel は色が変えられるので,クリックの動作を確認するが簡単です.



図1
実行開始
図2
[作成1] ボタンをクリック
  • パネル (TPanel) が 5 つできる
図3
パネルをクリックすると色が変わる

リスト2
複数のパネルをを実行時に作成して,配列で管理する例
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    //作成する個数が決まっている場合用の配列(静的配列)
    FPanelArray1 : array[0..4] of TPanel;
    //作成する個数を実行時に決める場合の配列(動的配列)
    FPannelArray2 : array of TPanel;
    procedure PanelClickEvent(Sender : TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  新規にパネルを作成
// 予め作成する個数(ここでは5つ)が決まっている場合
//  静的配列を使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LWidth : Integer;
  LHeight: Integer;
  i      : Integer;
begin
  //配列のパネルを解放破棄
  for i := 0 to  High(FPanelArray1) do begin
    FreeAndNil(FPanelArray1[i]);
  end;
  for i := 0 to  High(FPannelArray2) do begin
    FreeAndNil(FPannelArray2[i]);
  end;

  //パネルを作成
  LWidth  := 100;
  LHeight := 28;

  for i := 0 to High(FPanelArray1) do begin
    FPanelArray1[i] := TPanel.Create(Self);

    //Parent(配置場所)をSelf,つまりこのForm1とする
    //これがないと配置場所不明で表示されない
    FPanelArray1[i].Parent   := Self;

    FPanelArray1[i].Left     := 90;
    FPanelArray1[i].Width    := LWidth;
    FPanelArray1[i].Height   := LHeight;
    FPanelArray1[i].Top      := 40 + (LHeight + 3) * (i - 1);
    FPanelArray1[i].Color    := clSilver;
    FPanelArray1[i].Caption  := 'Panel ' + IntToStr(i + 1);

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    FPanelArray1[i].ParentBackground := False;

    //OnClickイベントをPanelClickEventというメソッドに割当てる
    FPanelArray1[i].OnClick := PanelClickEvent;
  end;
end;

//=============================================================================
//  新規にパネルを作成 
//  実行時に作成する個数を決める場合
//  動的配列を使用
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCount : Integer;
  LWidth : Integer;
  LHeight: Integer;
  i      : Integer;
begin
  //配列のパネルを解放破棄
  for i := 0 to  High(FPanelArray1) do begin
    FreeAndNil(FPanelArray1[i]);
  end;
  for i := 0 to  High(FPannelArray2) do begin
    FreeAndNil(FPannelArray2[i]);
  end;

  //パネルを5つ作成
  LCount := 5;
  //配列の要素数を決定
  SetLength(FPannelArray2, LCount);
  LWidth  := 100;
  LHeight := 28;

  for i := 0 to High(FPannelArray2) do begin
    FPannelArray2[i] := TPanel.Create(Self);

    //Parent(配置場所)をSelf,つまりこのForm1とする
    //これがないと配置場所不明で表示されない
    FPannelArray2[i].Parent   := Self;

    FPannelArray2[i].Left     := 90;
    FPannelArray2[i].Width    := LWidth;
    FPannelArray2[i].Height   := LHeight;
    FPannelArray2[i].Top      := 40 + (LHeight + 3) * (i - 1);
    FPannelArray2[i].Color    := clWhite;
    FPannelArray2[i].Caption  := 'Panel ' + IntToStr(i + 1);

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    FPannelArray2[i].ParentBackground := False;

    //OnClickイベントをButtonClickEventというメソッドに割当てる
    FPannelArray2[i].OnClick := PanelClickEvent;
  end;
end;

//=============================================================================
//  作成したパネルのOnClickイベント処理
//=============================================================================
procedure TForm1.PanelClickEvent(Sender: TObject);
begin
  TPanel(Sender).Color := RGB(Random(255), Random(255), Random(255));
end;

end.


上のコードで TPanel を新規に作成すると,それらは前項の説明で使用した Components のリストにも含まれます.それを実際に確認したのが下図です,新規に作成した TPanel には名前を付けていませんから番号だけとなっています.

これは,TForm1 というクラスに元々いた生徒に新規に数人の転入生の分が追加されたようなものです.転入生には別の配列,リスト,つまり別の名簿があるということになります.
名簿の i 番目がその生徒そのものです.



図4
フォーム上のコンポーネントのリスト

リスト3
複数の TPanel を新規に作成した後にフォーム上のコンポーネントをリスト
//=============================================================================
//  新規にパネルを作成
// 予め作成する個数(ここでは5つ)が決まっている場合
//  静的配列を使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LWidth : Integer;
  LHeight: Integer;
  i      : Integer;
begin
  //PnlArray2配列のパネルを解放破棄
  for i := 0 to  High(FPanelArray1) do begin
    FreeAndNil(FPanelArray1[i]);
  end;

  //パネルを作成
  LWidth  := 100;
  LHeight := 28;

  for i := 0 to High(FPanelArray1) do begin
    FPanelArray1[i] := TPanel.Create(Self);

    //Parent(配置場所)をSelf,つまりこのForm1とする
    //これがないと配置場所不明で表示されない
    FPanelArray1[i].Parent   := Self;

    FPanelArray1[i].Left     := 90;
    FPanelArray1[i].Width    := LWidth;
    FPanelArray1[i].Height   := LHeight;
    FPanelArray1[i].Top      := 40 + (LHeight + 3) * (i - 1);
    FPanelArray1[i].Color    := clSilver;
    FPanelArray1[i].Caption  := 'Panel ' + IntToStr(i + 1);

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    FPanelArray1[i].ParentBackground := False;
  end;
end;

//=============================================================================
//  Form1(Self)上に配置しているコンポーネントの列挙
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCompo : TComponent;
  i      : Integer;
begin
  ListBox1.Items.Clear;

  for i := 0 to Self.ComponentCount - 1 do begin
    LCompo := Self.Components[i];
    ListBox1.Items.Add(IntToStr(i) + '  ' + LCompo.Name);
  end;
end;




03_複数のボタンを作成 ( TList を使用 )


リストとして TList を使用する例です.
TList はポインタ格納用のリストです.ポインタですから,整数や実数,あるいは文字列,コンポーネントを含めたクラスのオブジェクトも格納てきます.要素の削除,追加も比較的容易に操作できます.リストとしての操作は,配列よりも柔軟性があります.

TList には TButton オブジェクトのポインタが格納され,そのポインタが示すアドレスに各 TButton オブジェクトの内容が格納されまます.TList は何のコンポーネント,オブジェクトであるかの情報を保持していません.そこで,オブジェクトの情報を取り出す時は,何のクラスのオブジェクトであるかを指定します.これをキャストすると言います.
下のコードでは,ボタンを格納している ButtonLis の i 番目のボタンを取り出すのに,以下のように TButton でキャストしています.


        TButton(FButtonList[i])

本サンプルでは,作成したボタンをクリックすると,クリックしたボタン自身を削除します.


図5
実行して [作成] ボタンをクリック
図6
生成したボタンをクリック
  • 自分自身が削除される

リスト4
複数のパネルをを実行時に作成して,TList で管理する例
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  生成したボタン用リストの破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FButtonList.Free;
end;

//=============================================================================
//  ボタンを5つ生成してTListに格納
//
//  ボタンを生成してTListに格納してから各プロパティを設定
//  各ボタンオブジェクトにアクセスするにはTButtonでキャストする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LButton : TButton;
  i       : Integer;
begin
  //もし作成済みだとエラーになるので一度解放
  if Assigned(FButtonList) then begin
    for i := 0 to FButtonList.Count - 1 do begin
      LButton := TButton(FButtonList[i]);
      FreeAndNil(LButton);
    end;
    FreeAndNil(FButtonList);
  end;

  //TButtonを生成して配列に格納
  FButtonList := TList.Create;
  for i := 0 to 4 do begin
    LButton := TButton.Create(Application);
    FButtonList.Add(LButton);
  end;

  //各ボタンの位置とサイズを設定
  for i := 0 to FButtonList.Count - 1 do begin
    TButton(FButtonList[i]).Parent  := Self;
    TButton(FButtonList[i]).Left    := 100;
    TButton(FButtonList[i]).Top     := 10 + i * 25;
    TButton(FButtonList[i]).Tag     := i + 1;
    //クリックイベント用のメソッドを設定
    TButton(FButtonList[i]).OnClick := ButtonClickEvent;
  end;
end;

//-----------------------------------------------------------------------------
//  作成したパネルのOnClickイベント処理
//  クリックしたボタン自身を削除
//-----------------------------------------------------------------------------
procedure TForm1.ButtonClickEvent(Sender: TObject);
var
  LIndex : Integer;
begin
  //何番目のボタンであるかを取得
  LIndex := FButtonList.IndexOf(TButton(Sender));

  //そのボタン部分のメモリを解放して,リストのそれを削除
  TButton(TButton(Sender)).Free;
  FButtonList.Delete(LIndex);
end;

end.




04_メッセージ処理によるコンポーネントの削除


コンポーネントの削除の部分は,メッセージを使用した処理でも可能です.
下のサンプルは,[Additional] タブの TApplicationEvents コンポーネントをフォームに配置し,イベントの OnMessage で処理します.


図7
設計時画面
  • TApplicationEvents コンポを配置

リスト5
複数のパネルをを実行時に作成して,TList で管理
コンポーネントの削除をメッセージで処理する例
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1 : TButton;
    ApplicationEvents1: TApplicationEvents;
    procedure FormDestroy(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG;
      var Handled: Boolean);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FButtonList: TList;
    procedure ButtonClickEvent(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  生成したボタン用リストの破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FButtonList.Free;
end;

//=============================================================================
//  ボタンを5つ生成してTListに格納
//
//  ボタンを生成してTListに格納してから各プロパティを設定
//  各ボタンオブジェクトにアクセスするにはTButtonでキャストする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LButton : TButton;
  i       : Integer;
begin
  //もし作成済みだとエラーになるので一度解放
  if Assigned(FButtonList) then begin
    for i := 0 to FButtonList.Count - 1 do begin
      LButton := TButton(FButtonList[i]);
      FreeAndNil(LButton);
    end;
    FreeAndNil(FButtonList);
  end;

  //TButtonを生成して配列に格納
  FButtonList := TList.Create;
  for i := 0 to 4 do begin
    LButton := TButton.Create(Application);
    FButtonList.Add(LButton);
  end;

  //各ボタンの位置とサイズを設定
  for i := 0 to FButtonList.Count - 1 do begin
    TButton(FButtonList[i]).Parent  := Self;
    TButton(FButtonList[i]).Left    := 100;
    TButton(FButtonList[i]).Top     := 10 + i * 25;
    TButton(FButtonList[i]).Tag     := i + 1;
    //クリックイベント用のメソッドを設定
    TButton(FButtonList[i]).OnClick := ButtonClickEvent;
  end;
end;

//-----------------------------------------------------------------------------
//  クリックしたボタン自身をメッセージ処理で削除
//-----------------------------------------------------------------------------
procedure TForm1.ButtonClickEvent(Sender: TObject);
var
  LIndex : Integer;
begin
  //何番目のボタンであるかを取得
  LIndex := FButtonList.IndexOf(TButton(Sender));

  PostMessage(0, WM_APP+100, WPARAM(TButton(Sender)), LIndex);
end;

//=============================================================================
//  受取ったメッセージの処理
//
//  送信する側では以下の値をセットしている
//  Wparam にはコンポーネントのインスタンス
//  Lparma にはこのコンポーネントが格納されているリストのインデックス
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  case Msg.message of
  WM_APP+100:
    begin
      TButton(Msg.WParam).Free;
      FButtonList.Delete(Msg.lParam);
    end;
  end;
end;

end.




05_複数のパネルを作成 ( TComponentList を使用 )


生成したコンポーネントを格納するリストに TComponentList を使用するサンプルです.
TCompnentList は TList の継承クラスで,TList にいろいろなプロパティやメソッドが追加され,より柔軟な操作が可能です.
TComponentList を使用すると,リスト内のコンポーネントを削除する際,該当コンポーネントの解放は必要ありません.TComponentList が自動で行ってくれます.


図8
実行開始画面
図9
[作成] ボタンをクリック
図10
[削除] ボタンを 2 回クリック

リスト6
複数のパネルをを実行時に作成して,TComponentList で管理する例
unit Unit1;

interface

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

type

  //生成するTPanelのItem
  TPanelItem = class (TPanel)
  protected
    procedure Click; override;
  end;

  //TPaneltemを管理するクラス
  TPanelList = class(TComponentList);

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネント格納用のリストを解放.
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FPanelList);
end;

//=============================================================================
//  新規にパネルを5つ作成
//
//  実行時にパネルを複数作成する例
//  TComponentListを使用して生成したコンポを管理
//  リストを使用する場合,名前を利用することないと思うが名前を付けている
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TPanelItem;
  LWidth  : Integer;
  LHeight : Integer;
  i       : Integer;
begin
  if Assigned(FPanelList) then FreeAndNil(FPanelList);

  //PanelItemを管理するTComponentListを作成
  //生成Itemオブジェクトを自動破棄するには引数をTureにする
  FPanelList := TPanelList.Create(True);

  LWidth  := 100;
  LHeight := 28;

  for i := 0 to 4 do begin
    LItem := TPanelItem.Create(nil);

    LItem.Parent   := Self;
    LItem.Left     := 90;
    LItem.Width    := LWidth;
    LItem.Height   := LHeight;
    LItem.Top      := 40+(LHeight+3)*(i-1);
    LItem.Color    := clSilver;
    LItem.Caption  := 'Panel ' + IntToStr(i+1);

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    LItem.ParentBackground := False;

    LItem.ChangeName(LItem.Caption);
    FPanelList.Add(LItem);
  end;
end;

//=============================================================================
//  Itemが2,つまり3番目のボタンを削除
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(FPanelList) then begin
    if FPanelList.Count > 2 then FPanelList.Delete(2);
  end;
end;

{ TPanelItem }

//-----------------------------------------------------------------------------
//  TPanelItemクラスのClickメソッド
//-----------------------------------------------------------------------------
procedure TPanelItem.Click;
begin
  inherited;
  Color := RGB(Random(255), Random(255), Random(255));
end;

end.




06_TComponentList の継承クラスを使用しない場合


上のコードでは,継承クラスを定義していて使用してます.
1 つはコンポーネントを格納するクラスとして,TComponentList を継承したクラス,もう 1 つは,生成する各コンポーネントである TPanel の継承クラスです.
そのような実装方法にしておけば,継承したクラスに,いろいろな操作用のプロパティやメソッドが追加できます.コンポーネントを一括処理するメソッド等の実装もできます.継承元の protected 部のメンバも使用可能となります.

本ページは,基本動作の確認用のサンプルとして掲載しています.基本動作確認としては必要ないかも知れませんが,サンプルコードを実際に利用する際の参考としてこのようにしています.


  //生成するTPanelのItem
  TPanelItem = class (TPanel)
  protected
    procedure Click; override;
  end;

  //TPaneltemを管理するクラス
  TPanelList = class(TComponentList);


以上のようなことが必要なければ,継承クラスを定義する必要はありません.
次のように,TPanel も TComponentList もインスタンスを直接生成して利用することになります.


リスト7
複数のパネルをを実行時に作成して,TComponentList で管理する例
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネント格納用のリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FPanelList);
end;

//=============================================================================
//  新規にパネルを5つ作成
//
//  実行時にパネルを複数作成する例
//  TComponentListを使用して生成したコンポを管理
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TPanel;
  LWidth  : Integer;
  LHeight : Integer;
  i       : Integer;
begin
  if Assigned(FPanelList) then FreeAndNil(FPanelList);

  //PanelItemを管理するTComponentListを作成
  //生成Itemオブジェクトを自動破棄するには引数をTureにする
  FPanelList := TComponentList.Create(True);

  LWidth  := 100;
  LHeight := 28;

  for i := 0 to 4 do begin
    LItem := TPanel.Create(nil);

    LItem.Parent   := Self;
    LItem.Left     := 90;
    LItem.Width    := LWidth;
    LItem.Height   := LHeight;
    LItem.Top      := 40+(LHeight+3)*(i-1);
    LItem.Color    := clSilver;
    LItem.Caption  := 'Panel ' + IntToStr(i+1);
    LItem.OnClick  := PanelClickEvent;

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    LItem.ParentBackground := False;

    //ChangeNameはprotected部のメンバなので使用できない
    //LItem.ChangeName(LItem.Caption);
    FPanelList.Add(LItem);
  end;
end;

//=============================================================================
//  Itemが2,つまり3番目のボタンを削除
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(FPanelList) then begin
    if FPanelList.Count > 2 then FPanelList.Delete(2);
  end;
end;

//-----------------------------------------------------------------------------
//  作成したパネルのOnClickイベント
//-----------------------------------------------------------------------------
procedure TForm1.PanelClickEvent(Sender: TObject);
begin
  TPanel(Sender).Color := RGB(Random(255), Random(255), Random(255));
end;

end.




07_既存のコンポーネントをコンポーネントリストに格納


TComponentList は既存のコンポーネントを格納して,リストとしての管理に使用できます.
本サンプルは,フォーム上のコンポーネントを TComponentList に格納して扱う例です.


図11
設計時画面
図12
[リスト作成] ボタンをクリック...
図13
[削除] ボタンをクリック
  • 3 番目のコンポーネントが削除される
  • リストを作成しないと削除できない

リスト8
既存のコンポーネントをコンポーネントのリストに格納して管理する例
unit Unit1;

interface

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

type
  //コンポーネントのリスト用のクラス
  TMyCompoList = class(TComponentList);

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    Panel5: TPanel;
    Button3: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Panel1Click(Sender: TObject);
  private
    { Private 宣言 }
    FCompoList : TMyCompoList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネントリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FCompoList);
end;

//=============================================================================
//  既存のコントロール(コンポーネント)をコンポーネントリストに格納
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  //コンポーネントのリストを削除
  if Assigned(FCompoList) then FreeAndNil(FCompoList);

  //TComponentListを作成
  //格納したオブジェクトを自動破棄するには引数をTureにする
  FCompoList := TMyCompoList.Create(True);

  //コンポーネントのリストに順番にコンポーネントを追加
  FCompoList.Add(Panel1);
  FCompoList.Add(Panel2);
  FCompoList.Add(Panel3);
  FCompoList.Add(Panel4);
  FCompoList.Add(Panel5);
  FCompoList.Add(Button3);
end;

//=============================================================================
//  生成したTPanelの共有OnClickイベント処理
//  当然であるがButton3の色は変わらない
//=============================================================================
procedure TForm1.Panel1Click(Sender: TObject);
begin
  TPanel(Sender).Color := RGB(Random(255), Random(255), Random(255));
end;

//=============================================================================
//  Itemが2,つまり3番目のコントロール(コンポーネント)を削除
//  リストを作成していなければ削除されない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(FCompoList) then begin
    if FCompoList.Count > 2 then FCompoList.Delete(2);
  end;
end;

end.




08_TImage プロパティを持つパネルを作成


ここまでのサンプルで,クラスを継承してコンポーネントを生成するサンプルがありす.その方法でコンポーネントを生成すると,生成するコンポーネントに別のコンポーネントを含めることができるようになります.本サンプルでは,TPanel に TImage コンポーネントを追加し,これをプロパティとして操作するようにしています.
このようなコンポーネント内部のコンポーネントもまた,実行時に生成するコンポーネントと扱い方は同じです.

このサンプルでは,TPanelItem という TPanel の継承,派生コンポーネントを作成して,プロパティや内部コンポーネントを設定しています.このように,派生コンポーネントを拡張する時は,この派生コンポーネントに Create メソッドを実装し,各種の初期化処理を行います.また,Destroy メソッドを実装して,このコンポーネントが破棄される時の処理を行います.
初期化の処理が必要なければ,Create は実装する必要はありません.また,終了時の処理が必要なければ Destroy メソッドは実装する必要はありません.

Create メソッドを一般に constructor (コンストラクタ),Destroy メソッドを destructor (デストラクタ) と言っています.これらを実装しない場合,コンポーネントは,継承元の constructor, 継承元の destructor 実行します.

実行時にコンポーネントを生成して利用する際,生成するコンポーネントに,何か少し機能を追加した場合,この程度のことでも,コンポーネントの利用の幅が広がります.



図14
設計時画面
図15
[作成] ボタンをクリック
  • パネルが 2 つ出来る
  • このパネルには TImage コントロールが載っている
図16
パネルをクリック
  • 画像の選択ダイアログが表示される
  • 画像ファイルを選択すると画像を表示

リスト9
TImageプロパティを持つパネルを実行時に作成
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Contnrs, ExtCtrls, ExtDlgs, Jpeg, GIFImg, PNGImage;

type
  //生成するPanelの継承クラス
  TPanelItem = class (TPanel)
  private
    FImage: TImage;
    procedure SetImage(const Value: TImage);
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    property Image : TImage read FImage write SetImage;
  end;

  //TPanelのリスト
  TPanelList = class(TComponentList);

  TForm1 = class(TForm)
    Button1: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FPanelList : TPanelList;
    procedure PanelClickEvent(Sender : TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TPanelItem }

//-----------------------------------------------------------------------------
//  FPanelListの各Item(TPanel)を生成する時にTImageも生成
//-----------------------------------------------------------------------------
constructor TPanelItem.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FImage        := TImage.Create(AOwner);
  FImage.Parent := Self;
end;

//-----------------------------------------------------------------------------
//  Imageプロパティの破棄
//  このコードがなくてもButton1Click内でTPanelList.Create(True);としているので
//  FImageは自動破棄となっている
//-----------------------------------------------------------------------------
destructor TPanelItem.Destroy;
begin
  FreeAndNil(FImage);
  inherited;
end;

//-----------------------------------------------------------------------------
//  TPanelItemクラスのImageプロパティの設定用メソッド
//  オブジェクト型のプロパティはAssignメソッドでセットする
//-----------------------------------------------------------------------------
procedure TPanelItem.SetImage(const Value: TImage);
begin
  Fimage.Assign(Value);
end;

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネントりリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FPanelList);
end;

//=============================================================================
//  新規にパネルを2つ作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TPanelItem;
  LWidth  : Integer;
  LHeight : Integer;
  i       : Integer;
begin
  if Assigned(FPanelList) then FreeAndNil(FPanelList);

  //PanelItemを管理するTComponentListを作成
  //生成Itemオブジェクトを自動破棄するには引数をTureにする
  FPanelList:=TPanelList.Create(True);

  LWidth  := 150;
  LHeight := 120;

  for i := 0 to 1 do begin
    LItem := TPanelItem.Create(Self);

    LItem.Parent     := Self;
    LItem.Width      := LWidth;
    LItem.Height     := LHeight;
    LItem.Left       := 10 + (LWidth + 10) * i;
    LItem.Top        := 40;
    LItem.BevelOuter := bvNone;
    LItem.Color      := clWhite;

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    LItem.ParentBackground := False;
    LItem.OnClick          := PanelClickEvent;
    LItem.ChangeName('pnl' + IntToStr(i + 1));

    //作成したTPanelのItem(LItem)のImageプロパティの設定
    //OnClickを定義し,Imageのクリックでも反応するようにしている
    LItem.Image.Left        := 10;
    LItem.Image.Width       := LItem.ClientWidth - LItem.Image.Left*2;
    LItem.Image.Top         := 10;
    LItem.Image.Height      := LItem.ClientHeight - LItem.Image.Top*2;
    LItem.Image.Transparent := True;
    LItem.Image.Stretch     := True;
    LItem.Image.OnClick     := PanelClickEvent;

    FPanelList.Add(LItem);
  end;
end;

//-----------------------------------------------------------------------------
//  作成したTPanelとTImageのOnClickイベント処理
//  画像選択ダイアログを表示して選択した画像を表示
//-----------------------------------------------------------------------------
procedure TForm1.PanelClickEvent(Sender: TObject);
var
  LFileName : String;
begin
  OpenPictureDialog1.InitialDir := ExtractFileDir(Application.ExeName);

  if OpenPictureDialog1.Execute then begin
    LFileName := OpenPictureDialog1.FileName;

    if (Sender is TImage) then begin
      TImage(Sender).Picture.LoadFromFile(LFileName);
    end else
    if (Sender is TPanelItem) then begin
      TPanelItem(Sender).Image.Picture.LoadFromFile(LFileName);
    end;
  end;
end;

end.




09_TPanel の継承クラスの Paint メソッドで画像を描画


上のサンプルでは TImage に画像を描画して表示していますが,TPanel には Canvas プロパティがあり,TImage を使用しなくても図形や画像を描画できます.
Canvas への描画は,Paint メソッドを実装して,そのメソッド内に書きます.この Paint メソッドは再描画が必要になると自動的に実行されるメソッドです.

このサンプルは,そのようにした TPanel コンポーネントを生成する例です.TPicture クラスのプロパティを実装しています.



リスト10
画像表示機能付きの TPanel コンポーネントを実行時に生成
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Contnrs, ExtCtrls, ExtDlgs, Jpeg, GIFImg, PNGImage;

type
  //生成するPanelの継承クラス
  TPanelItem = class (TPanel)
  private
    FPicture: TPicture;
    procedure SetPicture(const Value: TPicture);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    property Picture : TPicture read FPicture write SetPicture;
  end;

  //Panelのリスト
  TPanelList = class(TComponentList);

  TForm1 = class(TForm)
    Button1: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FPanelList : TPanelList;
    procedure PanelClickEvent(Sender : TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TPanelItem }

//-----------------------------------------------------------------------------
//  APanelListの各Item(TPanel)を生成する時にPictureも生成
//-----------------------------------------------------------------------------
constructor TPanelItem.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPicture := TPicture.Create;
end;

//-----------------------------------------------------------------------------
//  Pictureプロパティの破棄
//-----------------------------------------------------------------------------
destructor TPanelItem.Destroy;
begin
  FreeAndNil(FPicture);
  inherited;
end;

//-----------------------------------------------------------------------------
//  TPanelItemクラスのPaintメソッド
//-----------------------------------------------------------------------------
procedure TPanelItem.Paint;
begin
  inherited;
  if not Assigned(FPicture) then exit;

  Canvas.StretchDraw(ClientRect, FPicture.Graphic);
end;

//-----------------------------------------------------------------------------
//  TPanelItemクラスのPictureプロパティの設定用メソッド
//  オブジェクト型のプロパティはAssignメソッドでセットする
//-----------------------------------------------------------------------------
procedure TPanelItem.SetPicture(const Value: TPicture);
begin
  FPicture.Assign(Value);
end;

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネントりリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FPanelList);
end;

//=============================================================================
//  新規にパネルを2つ作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TPanelItem;
  LWidth  : Integer;
  LHeight : Integer;
  i       : Integer;
begin
  if Assigned(FPanelList) then FreeAndNil(FPanelList);

  //PanelItemを管理するTComponentListを作成
  //生成Itemオブジェクトを自動破棄するには引数をTureにする
  FPanelList := TPanelList.Create(True);

  LWidth  := 150;
  LHeight := 120;

  for i := 0 to 1 do begin
    LItem := TPanelItem.Create(Self);

    LItem.Parent     := Self;
    LItem.Width      := LWidth;
    LItem.Height     := LHeight;
    LItem.Left       := 10 + (LWidth + 10) * i;
    LItem.Top        := 40;
    LItem.BevelOuter := bvNone;
    LItem.Color      := clWhite;

    //これがないとTPanelのColorプロパティの設定が反映されない場合がある
    LItem.ParentBackground := False;
    LItem.OnClick          := PanelClickEvent;
    LItem.ChangeName('pnl' + IntToStr(i + 1));

    FPanelList.Add(LItem);
  end;
end;

//=============================================================================
//  作成したTPanelとTImageのOnClickイベント用メソッド
//  グラフィック選択ダイアログを表示して選択した画像を表示
//=============================================================================
procedure TForm1.PanelClickEvent(Sender: TObject);
var
  LFileName : String;
begin
  OpenPictureDialog1.InitialDir := ExtractFileDir(Application.ExeName);

  if OpenPictureDialog1.Execute then begin
    LFileName := OpenPictureDialog1.FileName;
    TPanelItem(Sender).Picture.LoadFromFile(LFileName);
    TPanelItem(Sender).Invalidate;
  end;
end;

end.




10_マウスカーソル下のコンポーネントを取得


マウスカーソルがある位置のコンポーネントを取得するサンプルです.
マウスカーソル下のコンポーネントは,FindVCLWindow 関数で取得できます.FindVCLWindow 関数は,ウィンドウハンドルを持つコントロールだけを検出できます.この関数の使用自体は,コンポーネントのリストとは関係ありません.
この他,マウスカーソル下のコントロールを取得する関数として FindDragTarget 関数があります.

本サンプルでは,クリックしたコンポーネントが上から何番目であるかを表示します.


図17
設計時画面
図18
[削除] ボタンクリック
  • 3 番目のコンポーネントが削除される
図19
[Panel4] をクリック
  • 3 番目のコンポーネントが削除されているので,Panel4 が 3 番目となる

リスト11
マウスカーソル位置のコンポーネントを取得
unit Unit1;

interface

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

type
  //コンポーネントのリスト用のクラス
  TMyCompoList = class(TComponentList);

  TForm1 = class(TForm)
    Button2: TButton;
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    Panel5: TPanel;
    Button3: TButton;
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Panel1Click(Sender: TObject);
  private
    { Private 宣言 }
    FCompoList : TMyCompoList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォームを表示する時の処理
//  既存のコントロール(コンポーネント)をコンポーネントリストに格納
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //TComponentListを作成
  FCompoList := TMyCompoList.Create(True);

  //コンポーネントのリストに順番にコンポーネントを追加
  FCompoList.Add(Panel1);
  FCompoList.Add(Panel2);
  FCompoList.Add(Panel3);
  FCompoList.Add(Panel4);
  FCompoList.Add(Panel5);
  FCompoList.Add(Button3);
end;

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネントリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FCompoList);
end;

//=============================================================================
//  生成したTPanelのOnClickイベント処理
//  クリック位置(正確にはマウスカーソル位置)のコントロールを表示
//  コントロールの名前はTWinControl(TControl).Name
//  コントロールの名前は,イベント内なのでTWinControl(Sender).Nameでも取得可能
//=============================================================================
procedure TForm1.Panel1Click(Sender: TObject);
var
  LPos     : TPoint;
  LControl : TWinControl;
  LIndex : Integer;
begin
  GetCursorPos(LPos);
  LControl := FindVCLWindow(LPos);

  if Assigned(FCompoList) then begin
    LIndex := FCompoList.IndexOf(LControl) + 1;
    MessageBox(Handle, PChar(IntToStr(LIndex) + ' 番目'), '情報', MB_ICONINFORMATION);
  end;
end;

//=============================================================================
//  Itemが2,つまり3番目のコントロール(コンポーネント)を削除
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(FCompoList) then begin
    if FCompoList.Count > 2 then FCompoList.Delete(2);
  end;
end;

end.




11_指定コンポーネント名のコンポを削除 ( FindComponent 使用 )


FindComponent 関数は,コンポーネントの名前からコンポーネントのインスタンスを取得するための関数です.ウィンドウハンドルの有無に関係なく検索できます.

本サンプルでは,指定した名前のコンポーネントを検索し,それを削除します.この動作そのものはコンポーネントのリストとは関係ありません.検索したコンポーネントは,コンポーネントのインスタンスを引数にして FreeAndNil 関数を実行すれば削除できます.指定した名前のコンポーネントを検索して,それをコンポーネントのリストから取得する際の参考用です.



図20
設計時画面
図21
[削除] ボタンをクリック
  • Panel2 という名前のコンポーネントを削除

リスト12
指定した名前のコンポーネントを検索して削除するサンプル
unit Unit1;

interface

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

type
  //コンポーネントのリスト用のクラス
  TMyCompoList = class(TComponentList);

  TForm1 = class(TForm)
    Button2: TButton;
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    Panel5: TPanel;
    Button3: TButton;
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FCompoList : TMyCompoList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォームを表示する時の処理
//  既存のコントロール(コンポーネント)をコンポーネントリストに格納
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //TComponentListを作成
  //格納したオブジェクトを自動破棄するには引数をTureにする
  FCompoList := TMyCompoList.Create(True);

  //コンポーネントのリストに順番にコンポーネントを追加
  FCompoList.Add(Panel1);
  FCompoList.Add(Panel2);
  FCompoList.Add(Panel3);
  FCompoList.Add(Panel4);
  FCompoList.Add(Panel5);
  FCompoList.Add(Button3);
end;

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネントリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FCompoList);
end;

//=============================================================================
//  コンポーネントリスト中のPanel2というコンポーネントを削除
//  FindComponentのテストサンプル
//
//  サンプルなのでわざわざインデックスを取得して削除しているが,以下で可能
//  FreeAndNil(LCompo);
//  FCompoList.Remove(LCompo);
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCompo : TComponent;
  LIndex : Integer;
begin
  LCompo := FindComponent('Panel2');
  if LCompo <> nil then begin
    LIndex := FCompoList.IndexOf(LCompo);
    FCompoList.Delete(LIndex);
  end;
end;

end.




12_ジェネリックスを使用したコンポーネントのリスト


以下のコードは,TComponentList を使用したサンプルと同じ動作を,ジェネリックスを使用して書き換えたものです.ジェネリックスを使用すると,該当するコンポーネントのクラスを指定して生成すれば,コンポーネントを参照する際,クラス型でキャストする必要がありません.ただし,削除の時に該当するコンポーネントの解放が必要です.ここでは,TList のジェネリックスを使用しています.

ジェネリックスは,Delphi 2009 以降で使用可能です.



リスト13
TList ジェネリックスを使用したコンポーネントのリスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Contnrs, ExtCtrls, Generics.Collections;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FPanelList : TList<TPanel>;
    procedure PanelClickEvent(Sender : TObject);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TForm1 }

//=============================================================================
//  フォーム破棄時の処理
//  コンポーネント格納用のリストを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  i : Integer;
begin
  if Assigned(FPanelList) then begin
    for i := 0 to FPanelList.Count - 1 do begin
      FPanelList[i].Free;
    end;
    FreeAndNil(FPanelList);
  end;
end;

//=============================================================================
//  新規にパネルを5つ作成
//  ジェネリックスを使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TPanel;
  LWidth  : Integer;
  LHeight : Integer;
  i       : Integer;
begin
  if Assigned(FPanelList) then begin
    for i := 0 to FPanelList.Count - 1 do begin
      FPanelList[i].Free;
    end;
    FreeAndNil(FPanelList);
  end;
  FPanelList := TList<TPanel>.Create;

  LWidth  := 100;
  LHeight := 28;

  for i := 0 to 4 do begin
    LItem := TPanel.Create(nil);

    LItem.Parent   := Self;
    LItem.Left     := 90;
    LItem.Width    := LWidth;
    LItem.Height   := LHeight;
    LItem.Top      := 40+(LHeight + 3) * (i - 1);
    LItem.Color    := clSilver;
    LItem.Caption  := 'Panel ' + IntToStr(i + 1);
    LItem.OnClick  := PanelClickEvent;
    LItem.ParentBackground := False;
    FPanelList.Add(LItem);
  end;
end;

//=============================================================================
//  1,つまり2番目のパネルを非表示にして
//  2,つまり3番目のパネルを削除
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  //要素そのものがTPanelなのでキャストは不要
  FPanelList[1].Visible := False;

  if Assigned(FPanelList) then begin
    if FPanelList.Count > 2 then begin
      FPanelList[2].Free;
      FPanelList.Delete(2);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  作成したパネルのOnClickイベント
//-----------------------------------------------------------------------------
procedure TForm1.PanelClickEvent(Sender: TObject);
begin
  TPanel(Sender).Color := RGB(Random(255), Random(255), Random(255));
end;

end.


TObjectList<T> を使用すると,配列要素のコンポーネントの削除の際,個々のコンポーネントの解放処理が必要なくなります.TObjectList<T> は,オブジェクトのエントリがリストから削除された際に自動的に解放する機能を持った TList<T> になっています.




  if Assigned(FPanelList) then begin
    FreeAndNil(FPanelList);
  end;