【问题标题】:Create and run TTimer in runtime在运行时创建并运行 TTimer
【发布时间】:2018-09-13 04:42:46
【问题描述】:

我试图在 Delphi 中实现类似于 Javascript 的 setTimeout() 过程的行为:延迟几秒钟后运行。为此,我在运行时创建了一个TTimer,运行它,然后释放它。

这是我的代码:

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;

但是我的代码无法编译,编译器返回如下错误:

[DCC 错误] unit1.pas(2153): E2010 不兼容的类型:'TNotifyEvent' 和 'procedure, untyped pointer or untyped parameter'"

有什么提示吗?

【问题讨论】:

  • 错误消息的意思是它所说的。 TNotifyEvent 的定义参见 OLH。您不能将嵌套过程作为一个过程使用。
  • 谢谢,我将程序移到主程序之外,但错误仍然存​​在
  • TNotifyEvent 是一个procedure of object,换句话说,它需要是一个类的方法,不是一个独立的过程,这就是我想象的你'我把它变成了。
  • 不要“sender.free”!请参阅“免费”文档。
  • 查看this问题的答案。

标签: delphi delphi-7


【解决方案1】:

TNotifyEvent 声明为:

TNotifyEvent = procedure(Sender: TObject) of object;

of object 使它成为一个闭包,它是一种特殊类型的方法指针,它带有两个指针——一个指向对象的指针和一个指向非静态类方法的指针调用对象。因此,您不能将独立函数(当然也不能是嵌套函数)直接分配给TNotifyEvent。这就是编译器所抱怨的。

因此,您需要声明一个类来包装您的 OnTimer 事件处理程序,例如:

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;

或者,您可以使用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;

或者,在 Delphi 2006 及更高版本中,您可以使用 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;

话虽这么说,IS 有一种方法可以使用独立函数,而完全不使用任何类包装器:

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;

【讨论】:

  • q 被标记为delphi-7 那么为什么CreateAnonymousThread 会适用?
  • @MartynA 我只是提供了多个选项,因为其他读者可能正在使用更现代的 Delphi 版本。我已经为你改写了。
【解决方案2】:

我使用了 Torry's 的这个过程作为延迟,它在执行时不会锁定线程:

procedure Delay(dwMilliseconds: Longint);
var
  iStart, iStop: DWORD;
begin
  iStart := GetTickCount;
  repeat
    iStop := GetTickCount;
    Application.ProcessMessages;
    Sleep(1);
  until (iStop - iStart) >= dwMilliseconds;
end;

【讨论】:

  • 在一个紧密的循环中调用ProcessMessages() 总是一个坏主意。我之前写过类似的循环,但我至少会使用GetQueueStatus()MsgWaitForMultipleObjects() 来知道何时需要调用ProcessMessages() 以及何时可以跳过它。但是,在这种情况下,我建议使用waitable timerWaitForSingleObject()/MsgWaitForMultipleObjects()RegisterWaitForSingleObject(),而不是使用GetTickCount()Sleep()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-29
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
相关资源
最近更新 更多