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;
Initialiselike that? It seems like it would have been more expressive to just declare it without generics:Initialise(MainForm: IFrmMainFormInterface). Of course, that still wouldn't allow you to assign the interface member to a standard method pointer, but I'm puzzled why you had the complex starting point in the first place.