はじめに
「あー、アレの名前なんて言うんだろうな?」 という、Delphi を使っていれば誰もが知っているけど、何て呼ぶのかわからないもののお話です。
コンポーネントの改造
「既存のコンポーネントの挙動をちょっとだけ変えたいけど、コンポーネントパッケージ作るまでもないかなぁ~」という時、
- クラスだけ書いてコンポーネント自体は動的作成する
- 名前解決ルールを利用してコンポーネントを差し替える
- クラスヘルパーで拡張する
という手法を採る事があります。とりあえず、今回はクラスヘルパーは考えないことにします。
■ クラスだけ書いてコンポーネント自体は動的作成する
例えば、Checked
プロパティを持つパネルのクラスを作ってみます。Checked
プロパティが True だとパネルの左上にマークが表示されます。
unit uCheckPanel;
interface
uses
System.Classes, Vcl.ExtCtrls, Vcl.Graphics;
type
TCheckPanel = class(TPanel)
private
FChecked: Boolean;
procedure SetChecked(const Value: Boolean);
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
property Checked: Boolean read FChecked write SetChecked;
end;
implementation
{ TCheckPanel }
constructor TCheckPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FChecked := False;
end;
procedure TCheckPanel.Paint;
begin
inherited Paint;
if FChecked then
begin
Self.Canvas.Brush.Color := clLime;
Self.Canvas.Pen.Color := clWhite;
Self.Canvas.Ellipse(4, 4, 14, 14);
end;
end;
procedure TCheckPanel.SetChecked(const Value: Boolean);
begin
if FChecked <> Value then
begin
FChecked := Value;
Self.Invalidate;
end;
end;
end.
元々 TPanel が 10 個貼られていたのであれば、これをすべて削除し、動的作成する事になります。コンポーネントを動的作成するという事は、フォームデザイナでポトリペタリできない事を意味します。
次のようなイベントハンドラを書き、パネルをクリックするたびにマークが点灯/消灯するようにします。
procedure TForm1.Panel_Click(Sender: TObject);
begin
(Sender as TCheckPanel).Checked := not (Sender as TCheckPanel).Checked;
end;
次のコードはフォーム作成時に TCheckPanel を 10 個作成します。
uses
..., uCheckPanel;
procedure TForm1.FormCreate(Sender: TObject);
begin
for var l:=0 to 1 do
for var i:=0 to 4 do
with TCheckPanel.Create(Self) do
begin
Parent := Self;
Width := 184;
Height := 40;
Top := i * 48 + 20;
Left := l * 192 + 32;
Name := Format('Panel%d', [l * 5 + i + 1]);
Caption := Name;
OnClick := Panel_Click;
end;
end;
■ 名前解決ルールを利用してコンポーネントを差し替える
動的作成だとフォームデザイナの恩恵を得られないため、ちょっと位置をズラしたいだけでもコードで修正しなくてはなりませんが、ちょっとしたトリックでフォームデザイナの恩恵を得る事ができます。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons, uCheckPanel;
type
TPanel = class(TCheckPanel); // <- ここ
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Panel4: TPanel;
Panel5: TPanel;
Panel6: TPanel;
Panel7: TPanel;
Panel8: TPanel;
Panel9: TPanel;
Panel10: TPanel;
procedure Panel_Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
...
TPanel は Vcl.ExtCtrls
で定義されています。TForm1 よりも前に TPanel = class(TCheckPanel);
という宣言をする事で、TPanel (Vcl.ExtCtrls) を TPanel という名前の TCheckPanel クラスで差し替える事が可能になります。
差し替える前 | 差し替えた後 |
---|---|
Vcl.ExtCtrl.TPanel | Unit1.TPanel (uCheckPanel.TCheckPanel) |
TCheckPanel が uCheckPanel.pas
の中で TPanel という名前で定義されていたとしても (名前が重複していたとしても)、TPanel = class(uCheckPanel.TPanel);
とする事ができます。
差し替える前 | 差し替えた後 |
---|---|
Vcl.ExtCtrl.TPanel | Unit1.TPanel (uCheckPanel.TPanel) |
但し、クラス名が重複する場合には uses でのユニットの順序が重要になってきます。この場合だと、uCheckPanel は Vcl.ExtCtrl よりも後に指定する必要があります。
uses
...,Vcl.ExtCtrls, ..., uCheckPanel;
パネルをクリックした時のイベントハンドラは次のようになります。このイベントハンドラは [オブジェクトインスペクタ] で割り当てます。
procedure TForm1.Panel_Click(Sender: TObject);
begin
(Sender as TPanel).Checked := not (Sender as TPanel).Checked;
end;
設計時:
実行時:
実行結果は動的作成時と変わらないので、唐突に VCL Style (Windows 11 Dark) を適用した場合のスクショを。
この、名前解決ルールを利用して差し替えるクラス
は インターポーザークラス (Interposer Classes) と呼ばれます。
See also:
おわりに
-
インターポーザークラス (Interposer Classes) という呼称は公式なものではないようで、DocWiki で検索しても一切ヒットしませんでした。
-
「日本語で紹介したものはないかな?」と探していたら、『OBJECT PASCAL HANDBOOK』に書かれていました 1。灯台下暗し。
-
誰が最初にインターポーザークラス という呼称を使いだしたかについてですが、『OBJECT PASCAL HANDBOOK』によると 1、『The Delphi Magazine』という書籍のようです (日本の『Delphi マガジン』とは別)。
See also:
- OBJECT PASCAL HANDBOOK 日本語版 (Amazon)
- OBJECT PASCAL HANDBOOK 日本語版 (達人出版会)
- OBJECT PASCAL HANDBOOK (Embarcadero)
- OBJECT PASCAL HANDBOOK 10.4 Sydney Edition (Embarcadero)
- OBJECT PASCAL HANDBOOK 11.0 Alexandria Edition (Embarcadero)
- Delphi Interceptor (/Interposer) Classes -> TButton = class(TButton) (Žarko Gajić)