Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年02月13日] [更新 2022年02月13日] Delphi 一般・その他
メソッドのポインタと手続きのポインタ
動作確認等 Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0652]







メソッドのポインタと手続きのポインタ


クラスの function, procedure (関数,手続き) はメソッドあるいはクラスのメソッドと呼ばれています.本ページのタイトルの「手続き」はクラスのメンバではない手続き型のことです.






01_メソッドのポインタと暗黙の引数


TStringList の LoadFromFile メソッドを使用する例です.テキストファイルを読み込んで TMemo に表示します.



図1
TStringList.LoadFromFile メソッドの使用例

リスト1
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;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  TStringList.LoadFromFile メソッドでテキストファイルを読み込む
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  LStrList := TStringList.Create;
  try
    LStrList.LoadFromFile('Hoge.txt');
    Memo1.Lines.Text := LStrList.Text;
  finally
    FreeAndNil(LStrList);
  end;
end;

end.


TStringList.LoadFromFile メソッドと同じ引数を持つ手続き型の変数を宣言して使用してみます.
コンパイルはエラーとなりまんが,実行時に下図のエラーとなります.



図2
メソッドをそのままい続きとして使用するとエラーになる

リスト2
//=============================================================================
//  TStringList.LoadFromFile メソッドでテキストファイルを読み込む
//  メソッドを手続きとして宣言
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LProc    : procedure(const FileName: string);
begin
  LStrList := TStringList.Create;
  try
    LProc := @TStringList.LoadFromFile;
    LProc('Hoge.txt');
    Memo1.Lines.Text := LStrList.Text;
  finally
    FreeAndNil(LStrList);
  end;
end;


手続き型に of Object を追加して,メソッド型のポインタ として宣言します.そうすれば正常にメソッドとして動作します.



図3
手続きをメソッドのポインタとして宣言

リスト3
//=============================================================================
//  TStringList.LoadFromFile メソッドでテキストファイルを読み込む
//  変数をメソッドのポインタ (オブジェクト) として宣言
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LProc    : procedure(const FileName: string) of Object;
begin
  LStrList := TStringList.Create;
  try
    LProc := LStrList.LoadFromFile;
    LProc('Hoge.txt');
    Memo1.Lines.Text := LStrList.Text;
  finally
    FreeAndNil(LStrList);
  end;
end;


手続き型の引数に,コントロール自身を示す引数を追加します.この手続きのポインタでも正常に動作します.
クラスのメソッドは,内部的にはこのような暗黙の引数を使用するように処理されます.



リスト4
//=============================================================================
//  TStringList.LoadFromFile メソッドでテキストファイルを読み込む
//  変数を暗黙の引数を使用した手続きとして宣言
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LProc    : procedure(Self: TStringList; const FileName: string);
begin
  LStrList := TStringList.Create;
  try
    LProc := @TStringList.LoadFromFile;
    LProc(LStrList, 'Hoge.txt');
    Memo1.Lines.Text := LStrList.Text;
  finally
    FreeAndNil(LStrList);
  end;
end;




02_コントロールのイベントに手続きを使用する


上の例のように,メソッドは手続きとして宣言して実行することができます.
このことを利用して,コントロールのイベントを手続きに置き換えることができます.イベントはプロパティであり型があります.また,ローカルで宣言しても意味がありません.そのような場合,TMethod を使用します.その例です.



図4
実行結果

リスト5
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;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  procedure OnMouseMoveEvent(Self: TObject; Sender: TObject; Shift: TShiftState; X,
    Y: Integer);
  procedure OnClickEvent(Self: TObject; Sender: TObject);

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  Memo1 (TMmoe) のイベントをクラスのメンバではない手続きに割り当てる例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMethod : TMethod;
begin
  LMethod.Code := @OnMouseMoveEvent;
   // Data メンバは,第 1 引数の Self を使用しなければ nil でもよい
   // クラス以外で設定する時は nil にでもしておく
   // 必要ならば実際のオブジェクトを渡す
  LMethod.Data := Self;
  Memo1.OnMouseMove := TMouseMoveEvent(LMethod);

  LMethod.Code := @OnClickEvent;
  LMethod.Data := Self;
  Memo1.OnClick := TNotifyEvent(LMethod);
end;

//-----------------------------------------------------------------------------
//  OnMouseMove イベント用の手続き
//-----------------------------------------------------------------------------
procedure OnMouseMoveEvent(Self: TObject; Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  LText : string;
begin
  LText := 'X : ' + IntToStr(X) + '  Y : ' + IntToStr(Y);
  Form1.Memo1.Lines.Add(LText);
end;

//-----------------------------------------------------------------------------
//  OnClick イベント用の手続き
//-----------------------------------------------------------------------------
procedure OnClickEvent(Self: TObject; Sender: TObject);
begin
  ShowMessage('Clicked !!   ' + TControl(Sender).Name);
end;

end.


TNotifyEven 型のイベントの場合は第 1 引数を省略しても動作します.この場合,Data メンバの値 (ポインタ) は Sender になります.したがって,Data メンバの値を nil にすると,イベントが発生したコントロールを特定できなくなります.
第 1 引数を省略しなければ,Sender は常にイベントの発生コントロールです.第 1 引数を省略しない方が便利と言えます.



図5
実行結果

リスト6
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;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  procedure OnClickEvent(Sender: TObject);

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  Panle1 の OnClick イベントをクラスのメンバではない手続きに割り当てる例
//
//  type
//    TNotifyEvent = procedure(Sender: TObject) of object;
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMethod : TMethod;
begin
  LMethod.Code := @OnClickEvent;
  LMethod.Data := Panel1;
  Panel1.OnClick := TNotifyEvent(LMethod);
end;

//-----------------------------------------------------------------------------
//  OnClick イベント用の手続き
//-----------------------------------------------------------------------------
procedure OnClickEvent(Sender: TObject);
begin
  ShowMessage('Clicked !!   ' + TControl(Sender).Name);
end;

end.