啥是計數器?
計時器是一種輸入設備,它週期性地在每經過一個指定的時間間隔後就通知應用程序一次。當你的程序將時間間隔告訴Windows,例如“每10秒鐘通知我一聲”,然後Windows給你的程序發送週期性發生的WM_TIMER消息以表示時間到了。
我們可以通過調用SetTimer函數爲的Windows程序分配一個定時器。SetTimer有一個時間間隔範圍爲1毫秒到 4,294,967,295毫秒(將近50天)的整型參數,這個值指示Windows每隔多久時間給程序發送WM_TIMER消息。例如,如果間隔爲 1000毫秒,那麼Windows將每秒給程序發送一個WM_TIMER消息。
當你的程序用完定時器時,它調用KillTimer函數來停止計時器消息。在處理WM_TIMER消息時,你可以通過調用KillTimer函數來 編寫一個“瞬間”的定時器。KillTimer調用除了會銷燬以前調用SetTimer創建的定時器事件,還會清除消息隊列中尚未被處理的WM_TIMER消息,從而使程序在調用KillTimer之後就不會再 接收到WM_TIMER消息。
下面就介紹一下計時器的使用方法吧。
計時器怎麼用?
如果你需要在整個程序執行期間都使用計時器,那麼你將得從WinMain函數中或者在處理WM_CREATE消息時調用SetTimer,並在退出 WinMain或響應WM_DESTROY消息時調用KillTimer。根據調用SetTimer時使用的參數,可以選擇以下兩種方法之一來使用計時 器。
方法一
這是最方便的一種方法,它讓Windows把WM_TIMER消息發送到應用程序的正常窗口過程中,SetTimer調用如下所示:
SetTimer (hwnd, 1, uiMsecInterval, NULL) ;
第一個參數是其窗口過程將接收WM_TIMER消息的窗口句柄。第二個參數是定時器ID,它是一個非0數值,在整個例子中假定爲1。第三個參數是一 個32位無符號整數,以毫秒爲單位指定一個時間間隔,一個60,000的值將使Windows每分鐘發送一次WM_TIMER消息。
你可以通過調用
KillTimer (hwnd, 1) ;
在任何時刻停止WM_TIMER消息(即使正在處理WM_TIMER消息)。此函數的第二個參數是SetTimer調用中所用的同一個定時器ID。在終止程序之前,你應該在響應WM_DESTROY消息中停止任何活動的定時器。
當你的窗口過程收到一個WM_TIMER消息①時,wParam參數等於定時器的ID值(上述情形爲1),lParam參數爲0。爲了使程序更具有可讀性,您可以使用#define敘述定義不同的定時器ID:
#define TIMER_SEC 1
#define TIMER_MIN 2
然後你可以使用兩個SetTimer調用來設定兩個定時器:
SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;
SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;
WM_TIMER的處理如下所示:
caseWM_TIMER:
switch (wParam)
{
case TIMER_SEC:
//每秒一次的處理
break ;
case TIMER_MIN:
//每分鐘一次的處理
break ;
}
return 0 ;
如果你想將一個已經存在的定時器設定爲不同的時間間隔,您可以簡單地用不同的時間值再次調用SetTimer。
方法二
設定計時器的第一種方法是把WM_TIMER消息發送到通常的窗口過程,而第二種方法是讓Windows直接將計時器消息發送給你程序的另一個函數。
接收這些計時器消息的函數被稱爲回調函數,這是一個在你的程序之中但是由Windows調用的函數(在第四回曾提到)。 你先告訴Windows此函數的地址,然後Windows調用此函數。這看起來也很熟悉,因爲程序的窗口過程實際上也是一種回調函數。當註冊窗口類時,要 將函數的地址告訴Windows,當發送消息給程序時,Windows會調用此函數。
像窗口過程一樣,回調函數也必須定義爲CALLBACK,因爲它是由Windows從程序的程序代碼段調用的。 callback函數的參數和callback函數的返回值取決於callback函數的目的。跟計時器有關的callback函數中,輸入參數與窗口過 程的輸入參數一樣。計時器callback函數不向Windows返回值。
我們把以下的callback函數稱爲TimerProc(你能夠選擇與其它一些用語不會發生衝突的任何名稱),它只處理WM_TIMER消息:
VOID CALLBACK TimerProc ( HWND hwnd, UINT message, UINT iTimerID, DWORDdwTime)
{
//處理WM_TIMER消息
}
TimerProc的參數hwnd是在調用SetTimer時指定的窗口句柄。Windows只把WM_TIMER消息 送給TimerProc,因此消息參數message總是等於WM_TIMER。iTimerID值是計時器ID,dwTimer值是與從 GetTickCount函數的返回值相容的值。這是自Windows啓動後所經過的毫秒數。
用第一種方法設定計時器時要求下面格式的SetTimer調用:
SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;
你使用回調函數處理WM_TIMER消息時,SetTimer的第四個參數由回調函數的地址取代,如下所示:
SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;
看個例子吧。
- #include <windows.h>
- #define ID_TIMER 1
- LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
- VOID CALLBACK TimerProc (HWND, UINT, UINT, DWORD ) ;
- int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
- {
- static char szAppName[] = " Timer Demo " ;
- HWND hwnd ;
- MSG msg ;
- WNDCLASS wndclass ;
- wndclass.style = CS_HREDRAW | CS_VREDRAW ;
- wndclass.lpfnWndProc = WndProc ;
- wndclass.cbClsExtra = 0 ;
- wndclass.cbWndExtra = 0 ;
- wndclass.hInstance = hInstance ;
- wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
- wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
- wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
- wndclass.lpszMenuName = NULL ;
- wndclass.lpszClassName = szAppName ;
- if (!RegisterClass (&wndclass))
- {
- MessageBox ( NULL, TEXT ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;
- return 0 ;
- }
- hwnd = CreateWindow ( szAppName, "Timer Demo",
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT,
- NULL, NULL, hInstance, NULL) ;
- ShowWindow (hwnd, iCmdShow) ;
- UpdateWindow (hwnd) ;
- while (GetMessage (&msg, NULL, 0, 0))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- return msg.wParam ;
- }
- LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- switch (message)
- {
- case WM_CREATE:
- SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ;
- return 0 ;
- case WM_DESTROY:
- KillTimer (hwnd, ID_TIMER) ;
- PostQuitMessage (0) ;
- return 0 ;
- }
- return DefWindowProc (hwnd, message, wParam, lParam) ;
- }
- VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
- {
- static BOOL fFlipFlop = FALSE ;
- HBRUSH hBrush ;
- HDC hdc ;
- RECT rc ;
- fFlipFlop = !fFlipFlop ;
- GetClientRect (hwnd, &rc) ;
- hdc = GetDC (hwnd) ;
- hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;
- //括號裏的這種判斷語句我估計大家都懂,就不解釋了
- FillRect (hdc, &rc, hBrush) ;
- ReleaseDC (hwnd, hdc) ;
- DeleteObject (hBrush) ;
- }
這裏計時器的時間間隔設定爲1秒。當它收到WM_TIMER消息時,它將顯示區域的顏色由藍色變爲紅色或由紅色變爲藍色。
程序在窗口過程處理WM_CREATE消息時設定計時器。在處理WM_TIMER消息處理期間,翻轉bFlipFlop的值並使窗口無效以產生 WM_PAINT消息。在處理WM_PAINT消息處理期間,通過調用GetClientRect獲得窗口大小的RECT結構,並通過調用 FillRect改變窗口的顏色。
計時器精確嗎?
很可惜它不精確,原因如下。
原因一:Windows計時器是PC硬件和ROM BIOS構造的計時器邏輯的一種相對簡單的擴展。回到Windows以前的MS-DOS編程,應用程序能夠通過捕獲稱爲timer tick的BIOS中斷來實現時鐘或計時器。這些中斷每54.915毫秒產生一 次,或者大約每秒18.2次。一些爲MS-DOS編寫的程序自己捕獲這個硬件中斷以實現時鐘和計時器。這是原始的IBM PC的微處理器頻率值4.772720 MHz被262144所除而得出的結果。Windows應用程序不攔截BIOS中斷,相反地,Windows本身處理硬件中斷,這樣應用程序就不必進行處 理。在Windows 98中,計時器與其下的PC計時器一樣具有55毫秒的分辨率,在MicrosoftWindows NT中,計時器的分辨率爲10毫秒。即Windows應用程序不能以高於這些分辨率的頻率(在Windows 98下,每秒18.2次,在Windows NT下,每秒大約100次)接收WM_TIMER消息。在SetTimer中指定的時間間隔總是截尾後tick數的整數倍。例如,1000毫秒的間隔除以 54.925毫秒,得到18.207個tick,截尾後是18個tick,它實際上是989毫秒。對每個小於55毫秒的間隔,每個tick都會產生一個 WM_TIMER消息。
可見,計時器並不能嚴格按照指定的時間間隔發送WM_TIMER消息,它總要相差那麼幾毫秒。
即使忽略這幾個毫秒的差別,計時器仍然不精確。請看原因二:
WM_TIMER消息放在正常的消息隊列之中,和其他消息排列在一起,因此,如果在SetTimer中指定間隔爲1000毫秒,那麼不能保證程序每 1000毫秒或者989毫秒就會收到一個WM_TIMER消息。如果其他程序的運行時間超過一秒,在此期間內,你的程序將收不到任何WM_TIMER消 息。事實上, Windows對WM_TIMER消息的處理非常類似於對WM_PAINT消息的處理,這兩個消息都是低優先級的,程序只有在消息隊列中沒有其他消息時才 接收它們。
WM_TIMER還在另一方面和WM_PAINT相似:Windows不能持續向消息隊列中放入多個 WM_TIMER消息,而是將多餘的WM_TIMER消息組合成一個消息。因此,應用程序不會一次收到多個這樣的消息,儘管可能在短時間內得到兩個 WM_TIMER消息。應用程序不能確定這種處理方式所導致的WM_TIMER消息“丟失”的數目。
可見,WM_TIMER消息並不能及時被應用程序所處理,WM_TIMER在消息隊列中的延誤可能就不能用毫秒來計算了。
由以上兩點,你不能通過在處理WM_TIMER時一秒一秒計數的方法來計時。如果要實現一個時鐘程序,可以使用系統的時間函數如 GetLocalTime ,而在時鐘程序中,計時器的作用是定時調用GetLocalTime獲得新的時間並刷新時鐘畫面,當然這個刷新的間隔要等於或小於1秒。
①WM_TIMER消息。
wParam爲計數器的ID:如果需要設定多個計時器,那麼對每個計時器都使用不同的計時器ID。wParam的值將隨傳遞到窗口過程的WM_TIMER消息的不同而不同。
lParam爲指向TimerProc的指針,如果調用SetTimer時沒有指定TimerProc(其參數值爲NULL,即第一種用法),則lParam爲0,顯然在第二種用法中此值就不爲0了。