Reputation: 7390
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?
Upvotes: 3
Views: 5714
Reputation: 2420
Actually, you're already very close. The error message is only somewhat misleading. Basically all you need to do is omit the parameter from the procedure call, and it will work. Here's an example of how that can look:
unit MyClass;
interface
uses
System.SysUtils,
Vcl.ExtCtrls;
type
MyClass = class
private
FTimer: TTimer;
procedure TMyClass.goTimer(Sender: TObject);
public
constructor Create;
destructor Destroy;
procedure TMyClass.createAndRunTimer;
end;
implementation
constructor TMyClass.Create
begin
FTimer := TTimer.Create;
end;
destructor TMyClass.Destroy
begin
FreeAndNil(FTimer);
end;
procedure TMyClass.runTimer;
begin
FTimer.Interval := 5000;
FTimer.OnTimer := goTimer; // omit the (t) here
FTimer.Enabled := true;
end;
procedure TMyClass.goTimer(Sender: TObject);
begin
FTimer.Enabled := false;
// do stuff here
end;
end.
Upvotes: 0
Reputation: 596156
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;
Upvotes: 10
Reputation: 25
I've used this procedure from Torry's as a delay, it doesn't lock the thread while executing:
procedure Delay(dwMilliseconds: Longint);
var
iStart, iStop: DWORD;
begin
iStart := GetTickCount;
repeat
iStop := GetTickCount;
Application.ProcessMessages;
Sleep(1);
until (iStop - iStart) >= dwMilliseconds;
end;
Upvotes: -1