传递函数而不是调用它(Delphi)

5

我有一个Delphi表单

TFrmMainForm = class(TForm, IFrmMainFormInterface)
  public
    procedure Display(Sender:TObject); 
end;

接口是指

IFrmMainFormInterface = interface
  procedure Display(Sender:TObject);
end;

还有另一个类

TMainFormViewModel = class
    strict private
      fTimer : TTimer;
      function GetOnTimer : TNotifyEvent;
      procedure SetOnTimer(timerEvent : TNotifyEvent);
    public
      property OnTimer : TNotifyEvent read GetOnTimer write SetOnTimer;
end;

implementation

function TMainFormViewModel.GetOnTimer : TNotifyEvent;
begin
    Result := fTimer.OnTimer;
end;

procedure TMainFormViewModel.SetOnTimer(timerEvent : TNotifyEvent);
begin
    fTimer.OnTimer := timerEvent;
end;

我有一个名为MainForm的表单实例和视图模型类MainFormViewModel

我想尝试:

MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display

问题在于它给我返回一个错误信息:

实际参数不足

我认为这是因为Delphi试图调用display函数而不是将其分配给OnTimer事件。我不确定如何解决这个问题,我尝试使用@运算符却没有成功。 编辑 我应该补充说明的是,MainForm在此函数中被声明为
procedure Initialise<T:Class, IFrmMainFormInterface>(MainForm : T);

procedure TController.Initialise<T>(MainForm : T);
begin
    MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display ;
end;

我认为DSharp有一些额外的功能来处理匿名函数,但我希望能够避免仅仅为此目的使用外部库。 - sav
你为什么要这样声明 Initialise?直接声明而不使用泛型似乎更具表现力:Initialise(MainForm: IFrmMainFormInterface)。当然,这仍然无法将接口成员分配给标准方法指针,但我很困惑为什么你一开始就选择了这种复杂的方式。 - Rob Kennedy
我正在尝试使用MVVM设计模式。在初始化过程中,我将表单绑定到ViewModel。binding.Bind<T>(MainFormViewModel, 'ScheduledWorkRecords'); MainForm.WorkRecords := binding; 为了测试目的,我还有一个模拟用户界面对象,可以代替实际的表单。 - sav
我正在使用的绑定类需要一个泛型类类型而不是接口来进行绑定(例如:我可以使用binding.Bind<IFrmMainFormInterface>(...))。 - sav
3个回答

5
MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display;

问题在于,您无法在此上下文中使用接口的方法。OnTimer事件是一个of object方法类型。它必须是一个对象或记录的方法。

2
问题在于接口方法引用与对象方法引用不兼容。
但是,不要直接传递对某个接口实现上的方法的引用,而是仅传递接口引用本身。在另一侧,在保留对要调用的特定方法的引用之前,请持有实现该方法的接口的引用。
然后,在适当的时间简单地调用目标方法。
事实上,在引用计数环境中这样做是至关重要的,因为对接口上的特定方法的引用将不会对接口本身的引用计数产生贡献...如果你只想维护对某些方法的引用,则到代码尝试调用该方法时,实现对象可能已经被销毁了(因为你没有维护任何对它的引用)。
如果需要引用实现接口的对象的任何方面,则在一个引用计数的上下文中,必须维护对该接口的引用。
此外,我建议分离关注点。即将表单响应计时器事件与其显示能力分开:
IfrmMainFormInterface = interface
[..guid..]
  procedure Display;
end;


ITimerListener = interface
[..guid..]
  procedure OnTimer(Sender: TObject);
end;


TMainFormViewModel = class
    strict private
      fTimer : TTimer;
      fOnTimer: ITimerListener;
      procedure DoOnTimer(Sender: TObject);  // internal event handler for fTimer.OnTimer
    public
      property OnTimer: ITimerListener read fOnTimer write fOnTimer;  // no need for getter/setter anymore
end;


procedure TMainFormViewModel.DoOnTimer(Sender: TObject);
begin
  // Enabling/disabling the timer might not be needed/or appropriate, but if it is
  //  then you can take care of that here, rather than relying on the listener to
  //  do it

  fTimer.Enabled := FALSE;
  try
    if Assigned(fOnTimer) then
       fOnTimer.OnTimer(self);  // call the method on the assigned listener interface

  finally
    fTimer.Enabled := TRUE;
  end;
end;



// Meanwhile, Somewhere in your view model initialisation....

fTimer.OnTimer := DoOnTimer;

然后,在您的TMainForm实现中:

TMainForm = class(TForm, IfrmMainFormInterface,
                         ITimerListener)
..
  procedure Display;
  procedure OnTimer(Sender: TObject);
..
end;


procedure TMainForm.OnTimer(Sender: TObject);
begin
  if Sender is TMainFormViewModel then
    Display;
end;

您可以使用接口类型属性将其“附加”到视图模型计时器,直接分配 主窗体本身 即可(这将导致传递正确类型的接口引用):
ViewModel.OnTimer := frmMain;

您可能已经注意到上面的示例中,视图模型将“self”作为“OnTimer”调用的Sender传递给监听器接口,而不是通过原始计时器对象传递。这是为了演示监听器可能如何使用Sender的类类型来(潜在地)区分其可能正在侦听的多个计时器源。
如果出现这种问题,有许多解决方法,其中之一就是利用您现在具有的特定接口监听器方法,该方法与底层事件方法类型(TNotifyEvent)的特定实现分离。因此,您可以根据需要引入任何其他参数到计时器监听器接口方法中,例如:如果您的视图模型具有多个计时器,则您的ITimerListener接口可能要求除Sender之外(或代替),还要传递计时器ID。
ITimerListener = interface
[..guid..]
  procedure OnTimer(Sender: TObject; aTimerID: Integer);
end;

1
我认为MainFormViewModel.OnTimer := MainForm.Display应该有效。无论如何,为什么要将实例强制转换为接口?

我已经尝试了这两种方式,但没有任何区别。似乎Delphi不支持以这种方式进行操作。 - sav
我也应该补充一下,MainForm变量在函数中声明为:procedure TController.Initialise<T>(MainForm: T); - sav
@sav,好的,这样就改变了问题的性质。 - iamjoosy

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