# 【Delphi】インターポーザークラス --- tags: Delphi プログラミング Pascal embarcadero objectpascal created_at: 2021-11-26 updated_at: 2024-11-27 --- # はじめに **「あー、アレの名前なんて言うんだろうな?」** という、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](./images/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](./images/6a73465a-9795-9ec0-eab1-9395432a321f.png) **実行時:** ![image.png](./images/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
(uCheckPanel.TCheckPanel) | TCheckPanel が `uCheckPanel.pas` の中で TPanel という名前で定義されていたとしても (名前が重複していたとしても)、`TPanel = class(uCheckPanel.TPanel);` とする事ができます。 | 差し替える前 | 差し替えた後 | |:---:|:-:| | Vcl.ExtCtrl.TPanel | Unit1.TPanel
(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](./images/e2c47483-1557-dfb4-3ecd-d96290a1e08d.png) **実行時:** 実行結果は動的作成時と変わらないので、唐突に **VCL Style** (Windows 11 Dark) を適用した場合のスクショを。 ![image.png](./images/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 マガジン』](./b581458db5140bd078cf.md)とは別)。 **See also:** - [OBJECT PASCAL HANDBOOK 日本語版 (Amazon)](https://www.amazon.co.jp/dp/487783401X/?tag=deko0f-22) - [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