二、Windows編程之定時器的使用:三種方法

如果您需要在整個程序執行期間都使用定時器,那麼您將得從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。如果需要設定多個定時器,那麼對每個定時器都使用不同的定時器ID。wParam的值將隨傳遞到窗口消息處理程序的WM_TIMER消息的不同而不同。爲了使程序更具有可讀性,您可以使用#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。在時鐘程序裏,如果顯示秒或不顯示秒是可以選擇的,您就可以這樣做,只需簡單地將時間間隔在1000毫秒和60 000毫秒間切換就可以了。

程序8-1顯示了一個使用定時器的簡單程序,名爲BEEPER1,定時器的時間間隔設定爲1秒。當它收到WM_TIMER消息時,它將顯示區域的顏色由藍色變爲紅色或由紅色變爲藍色,並通過呼叫MessageBeep函數發出響聲。(雖然MessageBeep通常用於MessageBox,但它確實是一個全功能的鳴叫函數。在有聲卡的PC機上,一般可以使用不同的MB_ICON參數作爲MessageBeep的一個參數以用於MessageBox,來播放使用者在「控制檯」的「聲音」程序中選擇的不同聲音)。

BEEPER1在窗口消息處理程序處理WM_CREATE消息時設定定時器。在處理WM_TIMER消息處理期間,BEEPER1呼叫MessageBeep,翻轉bFlipFlop的值並使窗口無效以產生WM_PAINT消息。在處理WM_PAINT消息處理期間,BEEPER1通過呼叫GetClientRect獲得窗口大小的RECT結構,並通過呼叫FillRect改變窗口的顏色。

程序8-1 BEEPER1

        BEEPER1.C
        
/*-------------------------------------------------------------------------
        
    BEEPER1.C  -- Timer Demo Program No. 1
        
                                          (c) Charles Petzold, 1998
        
-------------------------------------------------------------------------*/
        
#include <windows.h>
        

#define ID_TIMER    1
        

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
        
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
        
                                          PSTR szCmdLine, int iCmdShow)
        
{
        
    static TCHAR szAppName[] = TEXT ("Beeper1") ;
        
    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, TEXT ("Beeper1 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)
        
{
        
    static BOOL   fFlipFlop = FALSE ;
        
    HBRUSH                hBrush ;
        
    HDC                           hdc ;
        
    PAINTSTRUCT ps ;
        
    RECT        rc ;
        
   
        
    switch (message)
        
    {
        
    case   WM_CREATE:
        
            SetTimer (hwnd, ID_TIMER, 1000, NULL) ;
        
                  return 0 ;
        

           case   WM_TIMER :
        
                  MessageBeep (-1) ;        
        
                  fFlipFlop = !fFlipFlop ;
        
                  InvalidateRect (hwnd, NULL, FALSE) ;
        
                  return 0 ;
        
        
        
           case   WM_PAINT :
        
                  hdc = BeginPaint (hwnd, &ps) ;
        
        
        
                  GetClientRect (hwnd, &rc) ;
        
                  hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ;
        
                  FillRect (hdc, &rc, hBrush) ;
        

            EndPaint (hwnd, &ps) ;
        
           DeleteObject (hBrush) ;
        
                  return 0 ;
        
        
        
           case   WM_DESTROY :
        
                  KillTimer (hwnd, ID_TIMER) ;
        
                  PostQuitMessage (0) ;
        
           return 0 ;
        
           }
        
           return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}

因爲BEEPER1每次收到WM_TIMER消息時,都用顏色的變換顯示出來,所以您可以通過呼叫BEEPER1來查看WM_TIMER消息的性質,並完成Windows內部的一些其它操作。

例如,首先呼叫控制檯 顯示器程序,選擇效果,確定 拖曳時顯示窗口內容複選框沒有被選中。現在,試着移動或者縮放BEEPER1窗口,這將導致程序進入「模態消息循環」。Windows通過在內部消息而非您程序的消息循環中攔截所有消息,來禁止對移動或者縮放操作的任何干擾。通過此循環到達程序窗口的大多數消息都被丟棄,這就是BEEPER1停止蜂鳴的原因。當完成了移動與縮放之後,您將會注意到BEEPER1不能取得它所丟棄的所有WM_TIMER消息,儘管前兩個消息的間隔可能少於1秒。

在「拖曳時顯示窗口內容」複選框被選中時,Windows中,的模態消息循環會試圖給您的窗口消息處理程序傳遞一些丟失的消息。這樣做有時工作得很好,有時卻不行。

方法二

設定定時器的第一種方法是把WM_TIMER消息發送到通常的窗口消息處理程序,而第二種方法是讓Windows直接將定時器消息發送給您程序的另一個函數。

接收這些定時器消息的函數被稱爲「callback」函數,這是一個在您的程序之中但是由Windows呼叫的函數。您先告訴Windows此函數的地址,然後Windows呼叫此函數。這看起來也很熟悉,因爲程序的窗口消息處理程序實際上也是一種callback函數。當註冊窗口類別時,要將函數的地址告訴Windows,當發送消息給程序時,Windows會呼叫此函數。

SetTimer並非是唯一使用callback函數的Windows函數。CreateDialog和DialogBox函數(將在第十一章中介紹)使用callback函數處理對話框中的消息;有幾個Windows函數(EnumChildWindow、EnumFonts、EnumObjects、EnumProps和EnumWindow)把列舉信息傳遞給callback函數;還有幾個不那麼常用的函數(GrayString、LineDDA和SetWindowHookEx)也要求callback函數。

像窗口消息處理程序一樣,callback函數也必須定義爲CALLBACK,因爲它是由Windows從程序的程序代碼段呼叫的。callback函數的參數和callback函數的傳回值取決於callback函數的目的。跟定時器有關的callback函數中,輸入參數與窗口消息處理程序的輸入參數一樣。定時器callback函數不向Windows傳回值。

我們把以下的callback函數稱爲TimerProc(您能夠選擇與其它一些用語不會發生衝突的任何名稱),它只處理WM_TIMER消息:

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
        
{
        
           處理WM_TIMER消息
        
}

TimerProc的參數hwnd是在呼叫SetTimer時指定的窗口句柄。Windows只把WM_TIMER消息送給TimerProc,因此消息參數總是等於WM_TIMER。iTimerID值是定時器ID,dwTimer值是與從GetTickCount函數的傳回值相容的值。這是自Windows啓動後所經過的毫秒數。

在BEEPER1中已經看到過,用第一種方法設定定時器時要求下面格式的SetTimer呼叫:

SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;

您使用callback函數處理WM_TIMER消息時,SetTimer的第四個參數由callback函數的地址取代,如下所示:

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;

我們來看看一些範例程序代碼,這樣您就會了解這些東西是如何組合在一起的。在功能上,除了Windows發送一個定時器消息給TimerProc而非WndProc之外,程序8-2所示的BEEPER2程序與BEEPER1是相同的。注意,TimerProc和WndProc一起被聲明在程序的開始處。

程序8-2  BEEPER2
        
BEEPER2.C
        
/*---------------------------------------------------------------------------
        
  BEEPER2.C -- Timer Demo Program No. 2
        
                                   (c) Charles Petzold, 1998
        
---------------------------------------------------------------------------*/
        
#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[]           = "Beeper2" ;
        
           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, "Beeper2 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 ;
        
          
        
           MessageBeep (-1) ;
        
           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) ;
        
}

方法三

設定定時器的第三種方法類似於第二種方法,只是傳遞給SetTimer的hwnd參數被設定爲NULL,並且第二個參數(通常爲定時器ID)被忽略了,最後,此函數傳回定時器ID:

iTimerID = SetTimer (NULL, 0, wMsecInterval, TimerProc) ;

如果沒有可用的定時器,那麼從SetTimer傳回的iTimerID值將爲NULL。

KillTimer的第一個參數(通常是窗口句柄)也必須爲NULL,定時器ID必須是SetTimer的傳回值:

KillTimer (NULL, iTimerID) ;

傳遞給TimerProc定時器函數的hwnd參數也必須是NULL。這種設定定時器的方法很少被使用。如果在您的程序在不同時刻有一系列的SetTimer呼叫,而又不希望追蹤您已經用過了那些定時器ID,那麼使用此方法是很方便的。

既然您已經知道了如何使用Windows定時器,就可以開始討論一些有用的定時器程序了。

轉載自:http://c.biancheng.net/cpp/html/1280.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章