So I have a form in Delphi
TFrmMainForm = class(TForm, IFrmMainFormInterface)
public
procedure Display(Sender:TObject);
end;
The interface is
IFrmMainFormInterface = interface
procedure Display(Sender:TObject);
end;
And another class
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;
I have an instance of the Form MainForm and the view model class MainFormViewModel
and I want to try
MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display
The problem is this give me an error message
Not enough actual parameters
I belive this is because delphi is trying to call the display function rather than assign it to the OnTimer event. I'm not sure how to fix this, I've tried using the @ operator with no success.
EDIT
I should add that the MainForm is declared in this function as
procedure Initialise<T:Class, IFrmMainFormInterface>(MainForm : T);
procedure TController.Initialise<T>(MainForm : T);
begin
MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display ;
end;
MainFormViewModel.OnTimer := IFrmMainFormInterface(MainForm).Display;
The problem is that you cannot use a method of an interface in this context. The OnTimer
event is an of object
method type. It must be a method of an object or record.
The problem is that interface method references are not compatible with object method references.
But instead of directly passing a reference to a method on some interface implementation, simply pass the interface reference itself. On the other side of the fence, instead of keeping a reference to a specific method to be called, you hold the reference to the interface that implements that method.
You then simply call the target method at the appropriate time.
It is in fact crucial that you do this in a reference counted environment, since a reference to a specific method on an interface will NOT contribute to the reference count on the interface itself.... if you contrive to maintain only a reference to some method then by the time your code tries to call that method, the implementation object might have been destroyed (since you were not maintaining any reference to it).
If you need to refer to any aspect of some object that implements an interface, then - in a reference counted context - you must maintain a reference to that interface.
Additionally however, I would suggest a separation of concerns. i.e. separating the fact that your form responds to a timer event from its ability to be displayed:
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;
Then, in your TMainForm implementation:
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;
Which you "attach" to the view model timer using the interface type property by directly assigning the main form itself (which will result in an interface reference of the correct type being passed):
ViewModel.OnTimer := frmMain;
You might have noticed that in the above example, the view model passes "self" as the Sender of the OnTimer call to the listener interface, rather than passing through the originating timer object. This is in order to demonstrate how the listener might use the class type of the Sender to (potentially) discriminate between multiple timer sources that it may be listening to.
There are a number of ways of approaching that problem, if it arises, of which this is only one.
Another would be to take advantage of the fact that you now have a specific interface listener method for this purpose, separate from the specific implementation of the underlying event method type (TNotifyEvent). As a result you can introduce whatever additional parameters are required to your timer listener interface method as suits your needs. e.g. If your view models have multiple timers then your ITimerListener interface might contract that a Timer ID be passed in addition to (or instead of) the Sender, for example:
ITimerListener = interface
[..guid..]
procedure OnTimer(Sender: TObject; aTimerID: Integer);
end;
I think MainFormViewModel.OnTimer := MainForm.Display
should work. Why casting the instance to the Interface anyway ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With