Single shot timers

Dear Delphi Programmers,

I am looking for help how to write a timer with one shot (there is no graphical interface, so VCL timers are not discussed) ...

Let me explain a little more.

In my code (explaining with a VCL timer, but in this particular project I have no forms):

  • Call procedure that send char via serial port
  • Enable Timer with X Interval Size

In the OnTimer event:

I have a code that sends a char, and then disables the timer itself, so that it never gets executed again.

The problem is that I need to dynamically create these timers. I thought of the SetTimer() function, then KillTimer() in the OnTimer event, to disable it (for free).

Is this a good (safe) way?

Thanks!

+7
source share
3 answers

Is it safe to kill a timer from inside a timer event?

Yes, it is completely safe.

How to implement the simplest timer for one shot?

The simplest implementation of a 1-second timer with one shot is, but note: if you start more of them, you cannot distinguish which one has passed its interval:

 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; 
+14
source

The multimedia timer API provides single shot timer support. The advantage is that the time is much more accurate than the SetTimer / KillTimer solution, and you can use it at intervals of <50 ms. This happens at a cost since the callback is not returned in the context of the main thread. Here is my single shot timer implementation using the multimedia 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. 
+2
source

Do you understand that you do not need a graphical interface to use the VCL timer if you have a window handle? You can simply instantiate the code from

 fTimer := TTimer.Create(hWindowHandle); 

And even if you don’t have a window handle, you can create it by calling

 fVirtualWindowHWND := AllocateHWnd(WndMethod); 

but in this case you also need to write your own message loop. I know that calling the Windows API seems to be a simpler solution, but it also has its own quotes (for example, you cannot pass a class method to it ...), and I'm sure you can find out about it.

0
source

All Articles