サブクラス化コンポーネント

参照元1  参照元2


redball.gif目次へ戻る

n00401.gif

デフォルトで IDE が設計時に自動的に派生クラスを作ってくれるのは、TForm の下位クラスだけである。したがって、この TForm の派生クラスではメッセージハンドラを付け加えて、簡単に任意のメッセージを捕まえることができる。他のコンポーネントの場合は、派生クラスをつくってメッセージハンドラを追加するか、TControl の派生クラスの場合は WndProcoverride しなければならない。このようなときでも、VCL では TControl の派生クラスでは WindowProc プロパティーがあって、これを入れ替えることにより、簡単にサブクラス化してメッセージを捕獲できるようになっている。これは、VCL が用意してくれたサブクラス化するための機構である。ちょっとやってみよう。

右図のように、Panel1Button1、Edit1、CheckBox1 を置き、その下に Memo1 を置いて、Panel1 をサブクラス化してこれらのコントロールから送られる WM_COMMAND を捕まえてみよう。


以下に全リストを示す。

List 1

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    OriginalProc:TWndMethod;
    procedure SubClassProc(var Message:TMessage);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  OriginalProc := Panel1.WindowProc;
  Panel1.WindowProc := SubClassProc;
end;

procedure TForm1.SubClassProc(var Message:TMessage);
var
  Msg:TWMCommand;
begin
  OriginalProc(Message);
  case Message.Msg of
    WM_COMMAND:begin
      Msg := TWMCommand(Message);
      Memo1.Lines.Add(FindControl(Msg.Ctl).Name);
    end;
  end;
end;

end.
n00402.gif

結果は右図のようになり、クリックしたり Edit1 からフォーカスが移動したりすると、親である Panel1WM_COMMAND を通じてその通知メッセージを受け取っていることが分かるだろう。


WindowProc の入れ替えは Form1Create で行っている。

procedure TForm1.FormCreate(Sender: TObject);
begin
  OriginalProc := Panel1.WindowProc;
  Panel1.WindowProc := SubClassProc;
end;

そして、Panel1 のインスタンスが受け取るメッセージを SubClassProc で処理している。

procedure TForm1.SubClassProc(var Message:TMessage);
var
  Msg:TWMCommand;
begin
  OriginalProc(Message);
  case Message.Msg of
    WM_COMMAND:begin
      Msg := TWMCommand(Message);
      Memo1.Lines.Add(FindControl(Msg.Ctl).Name);
    end;
  end;
end;

ここでは最初に元の処理関数に渡し、それから WM_COMMAND をより分けて、どのコントロールが送ったかを FindControl() で見つけ、その NameMemo1 に追加している。

さて、このように簡単にサブクラス化ができるにしても、この一連の手続きは定型的なのでコンポーネントにカプセル化して、メッセージの処理をイベントハンドラの形にできれば便利だろう。ここでは、その一例を示す。以下のユニットファイルでしめす TSubClass は、上の一連の手続きを自動的に行う非ビジュアルコンポーネントである。

List 2

unit SubClassUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls;

type
TMessageBefore = procedure (Sender:TObject;var Message:TMessage;
                            var Handled:Boolean) of object;
TMessageAfter = procedure (Sender:TObject;var Message:TMessage
                           ) of object;

TSubClass = class(TComponent)
private
  OriginalProc:TWndMethod;
  FOnMessageAfter:TMessageAfter;
  FOnMessageBefore:TMessageBefore;
  FTarget:TControl;
  procedure SetTarget(Ctrl:TControl);
protected
  procedure SubClassProc(var Msg:TMessage);
public
  destructor Destroy;override;
  procedure Notification(AComponent: TComponent;
                               Operation: TOperation);override;
published
  property TargetControl:TControl read FTarget write SetTarget;
  property OnMessageBefore:TMessageBefore read FOnMessageBefore
                                          write FOnMessageBefore;
  property OnMessageAfter:TMessageAfter read FOnMessageAfter
                                          write FOnMessageAfter;
end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples',[TSubClass]);
end;

destructor TSubClass.Destroy;
begin
  SetTarget(nil);
  inherited;
end;

procedure TSubClass.SetTarget(Ctrl:TControl);
begin
  if FTarget<>nil then
  begin
    FTarget.WindowProc := OriginalProc;
    FTarget.RemoveFreeNotification(Self);
  end;

  if FTarget<>Ctrl then
  begin
    FTarget := Ctrl;
    if FTarget<>nil then
    begin
      FTarget.FreeNotification(Self);
      OriginalProc := Ctrl.WindowProc;
      Ctrl.WindowProc := SubClassProc;
    end;
  end;
end;

procedure TSubClass.SubClassProc(var Msg:TMessage);
var
  Handled:Boolean;
begin
  Handled := false;
  if Assigned(FOnMessageBefore) then FOnMessageBefore(self,Msg,Handled);
  if not Handled then OriginalProc(Msg);
  if Assigned(FOnMessageAfter) then FOnMessageAfter(self,Msg);
end;

procedure TSubClass.Notification(AComponent: TComponent;
                                 Operation: TOperation);
begin
  inherited;
  if (AComponent=FTarget) and (Operation=opRemove) then
    SetTarget(nil);
end;

end.
n00403.gif

右図のようにこのコンポーネントをインストールしてから、フォームのどこかに置き、オブジェクトインスペクタで TargetControl プロパティーのドロップダウンリストから Panel1 を選ぶ。そして OnMessageAfter イベントハンドラに処理を書く。


List 3

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    CheckBox1: TCheckBox;
    SubClass1: TSubClass;
    procedure SubClass1MessageAfter(Sender: TObject;
      var Message: TMessage);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.SubClass1MessageAfter(Sender: TObject;
  var Message: TMessage);
var
  Msg:TWMCommand;
begin
  case Message.Msg of
    WM_COMMAND:begin
      Msg := TWMCommand(Message);
      Memo1.Lines.Add(FindControl(Msg.Ctl).Name);
    end;
  end;
end;

end.

実行結果は、先ほどのものと全く同じになる。このようにして、簡単にサブクラス化することができる。しかし、同じコントロールを複数回サブクラス化するとやっかいなことになる。このようにサブクラス化して、特定のメッセージを捕まえて処理し、ある動作を実現することに成功したら、そしてもし、その動作がそれ以降でも必要になることが予測できたら、そのコードを参考にして派生コンポーネントを作るほうがよいだろう。TSubClass はそのようなとき、または一時的に特別な動作を実現したいときに便利に使えるだろう。

redball.gif目次へ戻る