I'm trying to achieve in Delphi a behavior similar to Javascript's setTimeout()
procedure : run things after a delay of some seconds. To do so, I'm creating a TTimer
at runtime, running it, and then free it.
Here is my code:
procedure createAndRunTimer();
procedure goTimer(Sender: TObject);
begin
(sender as ttimer).enabled := false;
// do stuff here
sender.free;
end;
var
t : TTimer;
begin
t := TTimer.Create(frmprinc);
t.Interval := 5000;
t.OnTimer := goTimer(t);
end;
But my code won't compile, the compiler returns the error below:
[DCC Error] unit1.pas(2153): E2010 Incompatible types: 'TNotifyEvent' and 'procedure, untyped pointer or untyped parameter'"
Any hints?
TNotifyEvent
is declared as:
TNotifyEvent = procedure(Sender: TObject) of object;
The of object
makes it a closure, which is a special type of method pointer that carries 2 pointers - an pointer to an object, and a pointer to a non-static class method which gets called on the object. As such, you cannot assign a standalone function, and certainly not a nested function, directly to a TNotifyEvent
. That is what the compiler is complaining about.
So, you need to declare a class to wrap your OnTimer
event handler, eg:
type
TTimerEvents = class
public
procedure goTimer(Sender: TObject);
end;
procedure TTimerEvents.goTimer(Sender: TObject);
begin
(Sender as TTimer).Enabled := false;
// do stuff here
// NOTE: you cannot destroy the Sender object from here, you must delay
// the destruction until after this handler exits! You can post a
// custom window message via PostMessage() and have the message handler
// call Sender.Free(). Or, you can use a worker thread to call
// Sender.Free() via TThread.Synchronize() (or TThread.Queue() in Delphi
// 8 and later). Or, in Delphi 10.2 Tokyo and later, you can call
// Sender.Free() via TThread.ForceQueue(). Or, use whatever other
// mechanism you want to use to call Sender.Free(), as long as it works
// asynchronously and calls Sender.Free() in the same thread that
// constructed the TTimer object ...
end;
var
events: TTimerEvents;
procedure createAndRunTimer();
var
t : TTimer;
begin
t := TTimer.Create(frmprinc);
t.Interval := 5000;
t.OnTimer := events.goTimer;
t.Enabled := True;
end;
initialization
events := TTimerEvents.Create;
finalization
events.Free;
Alternatively, you can use a class
method so you don't need an actual instance of the wrapper class:
type
TTimerEvents = class
public
class procedure goTimer(Sender: TObject);
end;
class procedure TTimerEvents.goTimer(Sender: TObject);
begin
(Sender as TTimer).Enabled := false;
// do stuff here
// delay-destroy the Sender as needed ...
end;
procedure createAndRunTimer();
var
t : TTimer;
begin
t := TTimer.Create(frmprinc);
t.Interval := 5000;
t.OnTimer := TTimerEvents.goTimer;
t.Enabled := True;
end;
Or, in Delphi 2006 and later, you can use a class helper:
type
TTimerHelper = class helper for TTimer
public
procedure goTimer(Sender: TObject);
end;
procedure TTimerHelper.goTimer(Sender: TObject);
begin
(Sender as TTimer).Enabled := false;
// do stuff here
// delay-destroy the Sender as needed ...
end;
procedure createAndRunTimer();
var
t : TTimer;
begin
t := TTimer.Create(frmprinc);
t.Interval := 5000;
t.OnTimer := t.goTimer;
t.Enabled := True;
end;
That being said, there IS a way to use a standalone function without using any class wrapper at all:
procedure goTimer(Self: Pointer; Sender: TObject);
begin
(Sender as TTimer).Enabled := false;
// do stuff here
// delay-destroy the Sender as needed ...
end;
procedure createAndRunTimer();
var
t : TTimer;
event : TNotifyEvent;
begin
t := TTimer.Create(frmprinc);
t.Interval := 5000;
TMethod(event).Data := nil; // or whatever you want to pass to the Self parameter...
TMethod(event).Code := @goTimer;
t.OnTimer := event;
t.Enabled := True;
end;