【Delphi】インターポーザークラス

# はじめに
**「あー、アレの名前なんて言うんだろうな？」** という、Delphi を使っていれば誰もが知っているけど、何て呼ぶのかわからないもののお話です。

# コンポーネントの改造
「既存のコンポーネントの挙動をちょっとだけ変えたいけど、コンポーネントパッケージ作るまでもないかなぁ～」という時、

 1. クラスだけ書いてコンポーネント自体は動的作成する
 2. 名前解決ルールを利用してコンポーネントを差し替える
 3. クラスヘルパーで拡張する

という手法を採る事があります。とりあえず、今回はクラスヘルパーは考えないことにします。

## ■ クラスだけ書いてコンポーネント自体は動的作成する
例えば、`Checked` プロパティを持つパネルのクラスを作ってみます。`Checked` プロパティが **True** だとパネルの左上にマークが表示されます。

```pascal:uCheckPanel.pas
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 個貼られていたのであれば、これをすべて削除し、動的作成する事になります。コンポーネントを動的作成するという事は、フォームデザイナでポトリペタリできない事を意味します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/e2c47483-1557-dfb4-3ecd-d96290a1e08d.png)

次のようなイベントハンドラを書き、パネルをクリックするたびにマークが点灯/消灯するようにします。

```pascal:Unit1.pas
procedure TForm1.Panel_Click(Sender: TObject);
begin
  (Sender as TCheckPanel).Checked := not (Sender as TCheckPanel).Checked;
end;
```

次のコードはフォーム作成時に TCheckPanel を 10 個作成します。

```pascal
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;
```

**設計時:**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/6a73465a-9795-9ec0-eab1-9395432a321f.png)
**実行時:**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/431c584e-5619-f43c-0bee-3fd5fb982ae8.png)

## ■ 名前解決ルールを利用してコンポーネントを差し替える
動的作成だとフォームデザイナの恩恵を得られないため、ちょっと位置をズラしたいだけでもコードで修正しなくてはなりませんが、ちょっとしたトリックでフォームデザイナの恩恵を得る事ができます。

```pascal:Unit1.pas
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<br>(uCheckPanel.TCheckPanel) |

TCheckPanel が `uCheckPanel.pas` の中で TPanel という名前で定義されていたとしても (名前が重複していたとしても)、`TPanel = class(uCheckPanel.TPanel);` とする事ができます。

| 差し替える前 | 差し替えた後 |
|:-:|:-:|
| Vcl.ExtCtrl.TPanel | Unit1.TPanel<br>(uCheckPanel.TPanel) |

但し、クラス名が重複する場合には **uses** でのユニットの順序が重要になってきます。この場合だと、uCheckPanel は Vcl.ExtCtrl よりも後に指定する必要があります。

```pascal:Unit1.pas
uses
  ...,Vcl.ExtCtrls, ..., uCheckPanel;

```

パネルをクリックした時のイベントハンドラは次のようになります。このイベントハンドラは \[オブジェクトインスペクタ\] で割り当てます。

```pascal:Unit1.pas
procedure TForm1.Panel_Click(Sender: TObject);
begin
  (Sender as TPanel).Checked := not (Sender as TPanel).Checked;
end;
```

**設計時:**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/e2c47483-1557-dfb4-3ecd-d96290a1e08d.png)
**実行時:**
実行結果は動的作成時と変わらないので、唐突に **VCL Style** (Windows 11 Dark) を適用した場合のスクショを。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/8ee1b0c9-9391-58e9-b5d4-d08d3339cd22.png)
この、`名前解決ルールを利用して差し替えるクラス`は **インターポーザークラス (Interposer Classes)** と呼ばれます。

**See also:**

 - [ユニット スコープ名 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88_%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E5%90%8D)
 - [Delphi での名前空間の使い方 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E3%81%A7%E3%81%AE%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)

# おわりに
 - **インターポーザークラス (Interposer Classes)** という呼称は公式なものではないようで、[DocWiki](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8) で検索しても一切ヒットしませんでした。

 - 「日本語で紹介したものはないかな？」と探していたら、『OBJECT PASCAL HANDBOOK』に書かれていました [^1]。灯台下暗し。

 - 誰が最初に**インターポーザークラス** という呼称を使いだしたかについてですが、『OBJECT PASCAL HANDBOOK』によると [^1]、[『The Delphi Magazine』](https://web.archive.org/web/20090318104352/http://thedelphimagazine.com/index.php)という書籍のようです (日本の[『Delphi マガジン』](https://qiita.com/ht_deko/items/b581458db5140bd078cf)とは別)。

**See also:**

 - [OBJECT PASCAL HANDBOOK 日本語版 (Amazon)](https://www.amazon.co.jp/dp/487783401X/)
 - [OBJECT PASCAL HANDBOOK 日本語版 (達人出版会)](https://tatsu-zine.com/books/object-pascal-handbook)
 - [OBJECT PASCAL HANDBOOK (Embarcadero)](http://forms.embarcadero.com/DownloadMarcoCantueBook)
 - [OBJECT PASCAL HANDBOOK 10.4 Sydney Edition (Embarcadero)](https://lp.embarcadero.com/Object-Pascal-Handbook-2021)
 - [OBJECT PASCAL HANDBOOK 11.0 Alexandria Edition (Embarcadero)](https://lp.embarcadero.com/ObjectPascalHandbookD11)
 - [Delphi Interceptor (/Interposer) Classes -> TButton = class(TButton) (Žarko Gajić)](https://zarko-gajic.iz.hr/delphi-interceptor-classes-tbutton-classtbutton/)

[^1]: マルコ・カントゥ (2016).「OBJECT PASCAL HANDBOOK」カットシステム, p.334