Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VCL events with anonymous methods - what do you think about this implementation?

Since anonymous methods appeared in Delphi I wanted to use them in VCL components events. Obviously for backward compatibility the VCL wasn't updated, so I managed to make a simple implementation with a few caveats.

type
  TNotifyEventDispatcher = class(TComponent)
  protected
    FClosure: TProc<TObject>;

    procedure OnNotifyEvent(Sender: TObject);
  public
    class function Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent; overload;

    function Attach(const Closure: TProc<TObject>): TNotifyEvent;
  end;

implementation

class function TNotifyEventDispatcher.Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent;
begin
  Result := TNotifyEventDispatcher.Create(Owner).Attach(Closure)
end;

function TNotifyEventDispatcher.Attach(const Closure: TProc<TObject>): TNotifyEvent;
begin
  FClosure := Closure;
  Result := Self.OnNotifyEvent
end;

procedure TNotifyEventDispatcher.OnNotifyEvent(Sender: TObject);
begin
  if Assigned(FClosure) then
    FClosure(Sender)
end;

end.

And this is how it's used for example:

procedure TForm1.FormCreate(Sender: TObject);
begin    
  Button1.OnClick := TNotifyEventDispatcher.Create(Self,
    procedure (Sender: TObject)
    begin
      Self.Caption := 'DONE!'
    end)
end;

Very simple I believe, there are two drawbacks:

  • I have to create a component to manage the lifetime of the anonymous method (I waste a bit more of memory, and it's a bit slower for the indirection, still I prefer more clear code in my applications)

  • I have to implement a new class (very simple) for every event signature. This one is a bit more complicated, still the VCL has very common event signatures, and for every special case when I create the class it's done forever.

What do you think of this implementation? Something to make it better?

like image 707
pragmatic_programmer Avatar asked Nov 06 '11 05:11

pragmatic_programmer


2 Answers

You can take a look at my multicast event implementation in DSharp.

Then you can write code like this:

function NotifyEvent(Owner: TComponent; const Delegates: array of TProc<TObject>): TNotifyEvent; overload;
begin
  Result := TEventHandler<TNotifyEvent>.Create<TProc<TObject>>(Owner, Delegates).Invoke;
end;

function NotifyEvent(Owner: TComponent; const Delegate: TProc<TObject>): TNotifyEvent; overload;
begin
  Result := NotifyEvent(Owner, [Delegate]);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.OnClick := NotifyEvent(Button1, [
    procedure(Sender: TObject)
    begin
      Caption := 'Started';
    end,
    procedure(Sender: TObject)
    begin
      if MessageDlg('Continue?', mtConfirmation, mbYesNo, 0) <> mrYes then
      begin
        Caption := 'Canceled';
        Abort;
      end;
    end,
    procedure(Sender: TObject)
    begin
      Caption := 'Finished';
    end]);
end;
like image 63
Stefan Glienke Avatar answered Nov 06 '22 04:11

Stefan Glienke


You could make TNotifyEventDispatcher to be a subclass of TInterfacedObject so you do not need to care about freeing it.

But to be more pragmatic one would use traditional event assignment that takes less lines of code and is supported by the IDE.

like image 42
andrius Avatar answered Nov 06 '22 06:11

andrius