2012-12-19 58 views
7

親愛的Delphi程序員,單次計時器

我尋求幫助,如何寫單次計時器(沒有GUI,因此VCL計時器沒問題的)...

讓我解釋一下多一點。

在我的代碼(與VCL計時器解釋,但在這個特定的項目我沒有形式):

  1. 呼叫一個procedure它通過串行端口
  2. 發送一個char啓用具有的X量的計時器Interval

OnTimer事件:

我有發送一個char然後禁用第一個碼e計時器本身永遠不會再被執行。

問題是我需要使這些定時器的動態創建。 我想到了「OnTimer事件」中的功能SetTimer(),然後KillTimer()來禁用它(釋放它)。

這是一個好的(安全的)方式嗎?

謝謝!

+0

我前段時間做過['類似的東西](http://stackoverflow.com/q/10468787/960757)。你的方法聽起來不錯,但請注意,如果你一次啓動多個計時器,你需要區分,如果你爲所有計時器使用了一個普通的回調函數,哪一個會觸發該超時。 – TLama

+0

我讀過'SetTimer()'函數,你可以把一個「唯一的ID」給定時器,並通過它們的ID來消滅 – ELCouz

+0

是的,這就是我在代碼中所做的。我正在存儲定時器ID和過程的集合,這些定時器ID和過程應該在定時器間隔過去時執行。發生這種情況時,我在該集合中搜索一個按計時器ID標識的項目,如果發現該項目,我殺死計時器,執行該過程並從集合中刪除找到的項目。 – TLama

回答

14

從定時器事件內部殺死定時器是否安全?

是的,這是完全安全的。

如何實現最簡單的一次性計時器?

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; 
+2

+1您的回答不能更清晰準確!謝謝!希望這可以幫助別人:) – ELCouz

+2

不客氣!只是一個旁註;當我們談論'TTimer'類以及從'OnTimer'事件中相當頻繁使用的禁用時,內部發生的情況是一樣的。實際上,每當你改變'Enabled'屬性的狀態時,定時器首先被'KillTimer'函數終止,而新的定時器可以由'SetTimer'函數創建。所以,這是'OnTimer'事件中'KillTimer'真正用法的一個例子。 – TLama

+4

問題說沒有GUI,在這種情況下,你不一定會認爲會有消息循環。基本的Windows定時器需要消息循環。 –

2

多媒體計時器API提供對單次計時器的支持。好處是,時序比SetTimer/KillTimer解決方案精確得多,您可以在間隔012毫秒的時間內使用它。這是有代價的,因爲回調不會在主線程的上下文中返回。 下面是使用多媒體計時器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. 
0

你是否意識到,你不必有一個圖形用戶界面,使用VCL計時器,只要你有一個窗戶把手?你可以簡單地通過

fTimer := TTimer.Create(hWindowHandle); 

實例化一個從代碼,即使你沒有一個窗口句柄可以通過調用

fVirtualWindowHWND := AllocateHWnd(WndMethod); 

創造之一,但在這種情況下,你也必須寫自己的消息循環。 我知道調用Windows API似乎是一個更簡單的解決方案,但它也有自己的洞穴(就像你不能傳遞一個類方法一樣......)),我想,你可能想知道這個。