delphirules
delphirules

Reputation: 7390

Create and run TTimer in runtime

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

Answers (3)

Kira Resari
Kira Resari

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

Remy Lebeau
Remy Lebeau

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

Dennis
Dennis

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

Related Questions