8

亲爱的德尔福程序员,

我正在寻求如何编写一次性计时器的帮助(没有 GUI,所以 VCL 计时器没有问题)......

让我再解释一下。

在我的代码中(用 VCL 计时器解释,但在这个特定项目中我没有表格):

  1. 调用procedure通过串口发送字符的 a
  2. 启用具有 X 数量的计时器Interval

OnTimer事件中:

我有一个代码,它发送一个字符然后禁用计时器本身不再被执行。

问题是我需要动态创建这些计时器。SetTimer()然后我想到了KillTimer()在“OnTimer 事件”中禁用它(释放它)的功能。

这是一种好的(安全)方式吗?

谢谢!

4

3 回答 3

15

从计时器事件内部杀死计时器是否安全?

是的,那是绝对安全的。

如何实现最简单的一次计时器?

1 秒一次性计时器的最简单实现是这样,但请注意,如果您启动更多计时器,您将无法区分其中哪个计时器经过了其间隔:

procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR;
  dwTime: DWORD); stdcall;
begin
  KillTimer(0, idEvent);
  ShowMessage('I''m done!');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetTimer(0, 0, 1000, @TimerProc);
end;
于 2012-12-19T01:57:03.347 回答
2

多媒体计时器 API 提供对一次性计时器的支持。好处是,计时比 SetTimer/KillTimer 解决方案要精确得多,您可以在间隔小于 50 毫秒的情况下使用它。这是有代价的,因为回调不会在主线程的上下文中返回。这是我使用多媒体定时器 API 实现的一次性定时器:

unit MMTimer;

interface
uses windows, Classes, mmsystem, SysUtils;
TOneShotCallbackEvent = procedure (const UserData: Pointer) of object;

(*
  The MMOneShotCallback function calls the Callback after the Interval passed.
  ** Attention: **
  The Callback is not called within the context of the main thread.
*)

type TMMOneShotTimer = class(TObject)
  private
    FTimeCaps: TTimeCaps;
    FResult: Integer;
    FResolution: Cardinal;
  public
    constructor Create;
    function MMOneShotCallback(const Interval: Cardinal; UserData: Pointer; Callback: TOneShotCallbackEvent): Boolean;
    property Result: Integer read FResult;
    property Resolution: Cardinal read FResolution;
end;
implementation
type
  TOneShotCallbackData = record
    Callback: TOneShotCallbackEvent;
   UserData: Pointer;
  end;
  POneShotCallbackData = ^TOneShotCallbackData;

procedure OneShotCallback(TimerID, Msg: UINT;
                    dwUser, dw1, dw2: DWord); pascal;
var pdata: POneShotCallbackData;
begin
  pdata := Pointer(dwUser);
  pdata.Callback(pdata.UserData);
  FreeMemory(pdata);
end;

constructor TMMOneShotTimer.Create;
begin
  FResult := timeGetDevCaps(@FTimeCaps, SizeOF(FTimeCaps));
  Assert(FResult=TIMERR_NOERROR, 'Call to timeGetDevCaps failed');
  FResolution := FTimeCaps.wPeriodMin;
  FResult := timeBeginPeriod(FResolution);
  Assert(FResult=TIMERR_NOERROR, 'Call to timeBeginPeriod failed');
end;

function TMMOneShotTimer.MMOneShotCallback(const Interval: Cardinal; UserData: Pointer; Callback: TOneShotCallbackEvent): Boolean;
var pdata: POneShotCallbackData;
begin
  GetMem(pdata, SizeOf(TOneShotCallbackData));
  pdata.Callback := Callback;
  pdata.UserData := UserData;
  result := (0 <> timeSetEvent(Interval, FResolution, @OneShotCallback, DWord(pdata), TIME_ONESHOT));
  if not result then
    FreeMemory(pdata);
  end;
end.
于 2012-12-19T06:39:28.550 回答
0

你是否意识到,只要你有一个窗口句柄,你就不必有一个 GUI 来使用 VCL 计时器吗?您可以简单地通过以下方式从代码中实例化一个

fTimer := TTimer.Create(hWindowHandle);

即使您没有窗口句柄,您也可以通过调用创建一个

fVirtualWindowHWND := AllocateHWnd(WndMethod);

但在这种情况下,您还必须编写自己的消息循环。我知道调用 Windows API 似乎是一个更简单的解决方案,但它也有它自己的警告(就像你不能将类方法传递给它......),我坚持,你可能想知道这个。

于 2013-06-03T14:08:52.197 回答