将TProc<TObject>转换为TNotifyEvent

19

参考这个帖子,其被接受的答案仍然非常神秘:

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

我想知道是否有可能设计出一种类似以下方式的最简单和优雅的方法:

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

为了达到相同的目的,其中AnonProc2NotifyEvent是Button所有者的一个方法,其签名如下:

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

这是可行的吗?如果是,如何实现?


你可能想看一下DSharp.Core.Events.pas - Stefan Glienke
@Stefan Glienke:谢谢你提醒我,我确实在我的电脑上安装了DSharp,但我错过了它(我很少使用它),但我相信自从它问世以来我一直在关注它(顺便说一下,我也是DelphiPraxis的成员,虽然不太擅长德语,但努力关注蓬勃发展的德国Delphi界)。当然,泛型编程是我接下来要学习的。谢谢你,Stevie :-) - menjaraz
2个回答

43

这将很容易地完成工作:

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;

AnonProc2NotifyEvent中的Owner参数是为了管理包装对象的生命周期。如果没有类似的参数,将会泄露TNotifyEventWrapper的实例。

将连接的事件所属的组件作为Owner参数传递。例如:

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

因此,当按钮被销毁时,TNotifyEventWrapper也会被销毁。包装对象必须至少与其关联的事件所属对象同样长寿。因此,选择Button1作为所有者是自然和明显的选择。


谢谢你,David!我从来没有想过一个包装器会做到这一点,它是组件化的一个很好的选择:简洁、清晰和优雅的解决方案。 - menjaraz
这很棒。我将其应用于TNetHttpClient异步模式。 - Nick Chan Abdullah
我也这么想。很美。 - Felipe Loredo

5

为了参考,我研究了Barry Kelly的博客post,这是在上述SO帖子中提到的,并得出了以下解决方案:

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;

仍然比较晦涩,但是封装后相对于最初的方法来说,使编码者的任务更容易。

我试图整理 MethRefToMethPtrMakeNotify 并将其放在一个方法中。

请注意,方法的签名发生了(轻微)变化,参数 aProc 变为 const


你仍然需要做一些事情来保持实现匿名过程接口的对象的活性。Barry提到了这个重要细节。 - David Heffernan
@David Heffernan:我需要在某个地方保留一个引用吗? - menjaraz
2
需要一个引用匿名方法来保持实现对象的存活。 - David Heffernan
当将匿名方法分配给TMethod.Data时,您可以手动增加refcount。只需记住在组件(Button1)不再使用事件时减少refcount即可,例如:type PInterface = ^IInterface; IInterface(TMethod(Result).Data) := PInterface(@aProc)^; ... IInterface(TMethod(Result).Data) := nil; - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接