2012-12-19 11 views
7

Drogi Delphi programistów,One-Shot Timers

szukam pomocy, jak napisać czasomierza One-Shot (nr GUI, więc VCL Timers z pytaniem) ...

Wytłumaczę nieco więcej.

W moim kodu (tłumacząc z VCL czasomierz, ale w tym konkretnym projekcie mam żadnych form):

  1. Skontaktować się z procedure który wysłać char nad portem szeregowym
  2. Włącz timer z kwotą X z Interval

W przypadku OnTimer:

mam kod, który następnie wysłać char wyłączyć th sam zegar, aby nigdy nie zostać ponownie wykonanym.

Problem polega na tym, że muszę sprawić, by tworzenie tych timerów było dynamiczne. Pomyślałem o funkcji SetTimer() następnie KillTimer() w "Zdarzeniu OnTimer", aby je wyłączyć (zwolnić).

Czy to jest dobry (bezpieczny) sposób?

Dziękujemy!

+0

Zrobiłem [coś podobnego] (http://stackoverflow.com/q/10468787/960757) jakiś czas temu. Twoje podejście brzmi dobrze, ale zauważ, że jeśli uruchomisz więcej niż jeden timer na raz, musisz rozróżnić, który z nich uruchomił ten limit czasu, jeśli używasz wspólnego procesu oddzwaniania dla wszystkich z nich. – TLama

+0

Czytałem, że funkcja 'SetTimer()' można umieścić "Unikalny identyfikator" na czasomierzu i zabić przez ich identyfikatory – ELCouz

+0

Tak, właśnie to robię w tym kodzie. Przechowuję kolekcję identyfikatorów i procedur czasomierza, które powinny być wykonywane po upłynięciu określonego czasu. Kiedy to się dzieje, przeszukuję w tej kolekcji element o identyfikatorze licznika upływającego czasu, a jeśli zostanie znaleziony, zabijam timer, wykonuję tę procedurę i usuwam znaleziony przedmiot z kolekcji. – TLama

Odpowiedz

14

Czy można bezpiecznie zabić zegar z wnętrza zdarzenia licznika czasu?

Tak, to jest całkowicie bezpieczne.

Jak wdrożyć najprostszy timer jednego strzału?

Najprostszym realizacja 1 drugi zegar jest jeden strzał, ale uwaga, że ​​jeśli zaczniesz więcej z nich nie będzie w stanie odróżnić, który z nich upłynął jej przedziale:

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 Twoja odpowiedź nie może być bardziej precyzyjna i precyzyjna! Dziękuję Ci! Mam nadzieję, że to pomoże komuś innemu :) – ELCouz

+2

Nie ma za co! Tylko sidenote; kiedy mówimy o klasie 'TTimer' i jej dość często używanym wyłączeniu z zdarzenia' OnTimer', zdarza się wewnętrznie to samo. Właściwie, gdy zmienisz stan właściwości 'Enabled', timer jest najpierw zabijany przez funkcję' KillTimer', a nowa jest opcjonalnie tworzona przez funkcję 'SetTimer'. Jest to więc jeden przykład rzeczywistego użycia 'KillTimer' ze zdarzenia' OnTimer'. – TLama

+4

Pytanie brzmi, że nie ma GUI, w takim przypadku nie można zakładać, że będzie pętla wiadomości. Podstawowe czasomierze systemu Windows wymagają pętli komunikatów. –

2

Interfejs API zegara multimedialnego zapewnia obsługę czasomierza jednorazowego użytku. Zaletą jest to, że czas jest znacznie bardziej precyzyjny niż rozwiązanie SetTimer/KillTimer i można z niego korzystać w przedziałach czasowych < 50 ms. To ma swoją cenę, ponieważ wywołanie zwrotne nie powraca w kontekście głównego wątku. Oto moja realizacja zegarem one-shot za pomocą multimedialnej czasomierza 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

Czy zdajesz sobie sprawę, że nie trzeba mieć GUI, aby użyć timera VCL tak długo, jak masz klamka okienna? Można po prostu instancję jednego z kodu przez

fTimer := TTimer.Create(hWindowHandle); 

A nawet jeśli nie mają klamki można utworzyć poprzez wywołanie

fVirtualWindowHWND := AllocateHWnd(WndMethod); 

ale w tym przypadku trzeba również napisać własna pętla wiadomości. Wiem, że wywoływanie Windows API wydaje się łatwiejsze, ale ma też swoje własne jaskinie (jak nie można przekazać mu metody klasowej ...), i myślałem, możesz chcieć o tym wiedzieć.

Powiązane problemy