10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DelphiAdvent Calendar 2021

Day 2

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

Last updated at Posted at 2021-12-01

はじめに

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

コンポーネントの改造

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

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

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

■ クラスだけ書いてコンポーネント自体は動的作成する

例えば、Checked プロパティを持つパネルのクラスを作ってみます。Checked プロパティが True だとパネルの左上にマークが表示されます。

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

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

Unit1.pas
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;

設計時:
image.png
実行時:
image.png

■ 名前解決ルールを利用してコンポーネントを差し替える

動的作成だとフォームデザイナの恩恵を得られないため、ちょっと位置をズラしたいだけでもコードで修正しなくてはなりませんが、ちょっとしたトリックでフォームデザイナの恩恵を得る事ができます。

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 よりも後に指定する必要があります。

Unit1.pas
uses
  ...,Vcl.ExtCtrls, ..., uCheckPanel;

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

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

設計時:
image.png
実行時:
実行結果は動的作成時と変わらないので、唐突に VCL Style (Windows 11 Dark) を適用した場合のスクショを。
image.png
この、名前解決ルールを利用して差し替えるクラスインターポーザークラス (Interposer Classes) と呼ばれます。

See also:

おわりに

  • インターポーザークラス (Interposer Classes) という呼称は公式なものではないようで、DocWiki で検索しても一切ヒットしませんでした。

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

  • 誰が最初にインターポーザークラス という呼称を使いだしたかについてですが、『OBJECT PASCAL HANDBOOK』によると 1『The Delphi Magazine』という書籍のようです (日本の『Delphi マガジン』とは別)。

See also:

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

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?