デフォルトで IDE が設計時に自動的に派生クラスを作ってくれるのは、TForm の下位クラスだけである。したがって、この TForm の派生クラスではメッセージハンドラを付け加えて、簡単に任意のメッセージを捕まえることができる。他のコンポーネントの場合は、派生クラスをつくってメッセージハンドラを追加するか、TControl の派生クラスの場合は WndProc を override しなければならない。このようなときでも、VCL では TControl の派生クラスでは WindowProc プロパティーがあって、これを入れ替えることにより、簡単にサブクラス化してメッセージを捕獲できるようになっている。これは、VCL が用意してくれたサブクラス化するための機構である。ちょっとやってみよう。
右図のように、Panel1 に Button1、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.
結果は右図のようになり、クリックしたり Edit1 からフォーカスが移動したりすると、親である Panel1 が WM_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() で見つけ、その Name を Memo1 に追加している。
さて、このように簡単にサブクラス化ができるにしても、この一連の手続きは定型的なのでコンポーネントにカプセル化して、メッセージの処理をイベントハンドラの形にできれば便利だろう。ここでは、その一例を示す。以下のユニットファイルでしめす 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.
右図のようにこのコンポーネントをインストールしてから、フォームのどこかに置き、オブジェクトインスペクタで 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 はそのようなとき、または一時的に特別な動作を実現したいときに便利に使えるだろう。