19

Further to this post whose accepted answer remains very cryptic:

@Button1.OnClick := pPointer(Cardinal(pPointer( procedure (sender: tObject) begin ((sender as TButton).Owner as TForm).Caption := 'Freedom to anonymous methods!' end )^ ) + $0C)^;

I wonder wether it is possible to devise a simplest and elegant way akin to:

Button.OnClick :=
                    AnonProc2NotifyEvent (
                    procedure (Sender: TObject)
                    begin
                      ((Sender as TButton).Owner as TForm).Caption := 'Freedom to anonymous methods!'
                    end
                      );

so as to attain the same purpose and where AnonProc2NotifyEvent is a method of the owner of Button with the following signature:

TOwnerOfButton = class(TForm)
  Button: TButton;
  ...
private
  ...
protected
  function AnonProc2NotifyEvent(aProc: TProc<TObject>): TNotifyEvent;
public
  ...
end;

Is that feasible and if so how to implement it ?

Community
  • 1
  • 1
menjaraz
  • 7,551
  • 4
  • 41
  • 81
  • You might want to take a look at [DSharp.Core.Events.pas](http://code.google.com/p/delphisorcery/source/browse/trunk/Source/Core/DSharp.Core.Events.pas). – Stefan Glienke Jul 15 '12 at 14:11
  • @Stefan Glienke: Thank you for reminding it to me, I have indeed DSharp installed on my box and I missed it (to my dismiss, I do not use often it so often) but believe I follow it since its inception (Bwt I'm also member of DelphiPraxis, not very good at German but strive to keep an eye to the buoyant German Delphi scene). For sure Generics is my next step. Thank you *Stevie* :-) – menjaraz Jul 15 '12 at 14:27

2 Answers2

42

This will do the job readily enough:

type
  TNotifyEventWrapper = class(TComponent)
  private
    FProc: TProc<TObject>;
  public
    constructor Create(Owner: TComponent; Proc: TProc<TObject>);
  published
    procedure Event(Sender: TObject);
  end;

constructor TNotifyEventWrapper.Create(Owner: TComponent; Proc: TProc<TObject>);
begin
  inherited Create(Owner);
  FProc := Proc;
end;

procedure TNotifyEventWrapper.Event(Sender: TObject);
begin
  FProc(Sender);
end;

function AnonProc2NotifyEvent(Owner: TComponent; Proc: TProc<TObject>): TNotifyEvent;
begin
  Result := TNotifyEventWrapper.Create(Owner, Proc).Event;
end;

The Owner parameter in AnonProc2NotifyEvent is so that the lifetime of the wrapper object can be managed. Without something like that you would leak instances of TNotifyEventWrapper.

Pass as Owner, the component to which you are connecting the event. For example:

Button1.OnClick := AnonProc2NotifyEvent(
  Button1,
  procedure(Sender: TObject)
  begin
    (Sender as TButton).Caption := 'Clicked';
  end
);

So, when the button is destroyed, the TNotifyEventWrapper will also be destroyed. The wrapper object must live at least as long as the object to whose events it is associated. And so the choice of Button1 as the owner is the natural and obvious one.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
5

For reference this what I am getting at, I studied Barry Kelly's blog post referenced in the prior SO post mentioned above and came up with this solution:

function TMainForm.Proc2NotifyEvent(const aProc: TNotifyReference): TNotifyEvent;
type
  TVtable = array[0..3] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  TMethod(Result).Code := PPVtable((@aProc)^)^^[3];
  TMethod(Result).Data := Pointer((@aProc)^);
end;

Still cryptic but encapsuled, so easing the task of the coder compared to the initial method.

I tried to tidy MethRefToMethPtr and MakeNotify and put it all in one method.

Notice that there was (a slight) change in the method's signature, the argument aProc became const.

Community
  • 1
  • 1
menjaraz
  • 7,551
  • 4
  • 41
  • 81
  • You'd still need to do something to keep alive the object that implements the anon proc interface. Barry does mention that important detail. – David Heffernan Jul 15 '12 at 14:21
  • @David Heffernan: Should I keep a reference somewhere? – menjaraz Jul 15 '12 at 14:34
  • 2
    There needs to be a reference to the anon method to keep the implementing object alive – David Heffernan Jul 15 '12 at 14:44
  • You could manually increment the refcount when assigning the anon method to `TMethod.Data`. You would just have to remember to decrement the refcount when the component (`Button1`) is no longer using the event, eg: `type PInterface = ^IInterface; IInterface(TMethod(Result).Data) := PInterface(@aProc)^; ... IInterface(TMethod(Result).Data) := nil;` – Remy Lebeau May 03 '19 at 22:29