說明
一種可以接受的方法是設定一個Windows計時器,給視窗程式傳送WM_TIMER訊息(我將在第八章中討論計時器)。對於每條WM_TIMER訊息,您使用GetDC取得一個裝置內容,畫一個隨機的矩形,然後用ReleaseDC釋放裝置內容。但是這樣又降低了程式的趣昧性,因為程式不能儘可能快地畫隨機矩形,它必須等待WM_TIMER訊息,而這又依賴於系統時鐘的解析度。在Windows中一定有很多「閒置時間」,在這個時間內,所有訊息佇列為空,Windows只停在一個小迴圈中等待鍵盤或者滑鼠輸入。我們能否在閒置時間內獲得控制,繪製矩形,並且只在有訊息加入程式的訊息佇列之後才釋放控制呢?這就是PeekMessage函式的目的之一。下面是PeekMessage呼叫的一個例子:
PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) ;
前面的四個參數(一個指向MSG結構的指標、一個視窗代號、兩個值指示訊息範圍)與GetMessage的參數相同。將第二、三、四個參數設定為NULL或0時,表明我們想讓PeekMessage傳回程式中所有視窗的所有訊息。如果要將訊息從訊息佇列中刪除,則將PeekMessage的最後一個參數設定為PM_REMOVE。如果您不希望刪除訊息,那么您可以將這個參數設定為PM_NOREMOVE。這就是為什麼Peek_Message是「偷看」而不是「取得」的原因,它使得程式可以檢查程式的佇列中的下一個訊息,而不實際刪除它。
GetMessage不將控制傳回給程式,直到從程式的訊息佇列中取得訊息,但是PeekMessage總是立刻傳回,而不論一個訊息是否出現。當訊息佇列中有一個訊息時,PeekMessage的傳回值為TRUE(非0),並且將按通常方式處理訊息。當佇列中沒有訊息時,PeekMessage傳回FALSE(0)。
這使得我們可以改寫普通的訊息迴圈。我們可以將如下所示的迴圈:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
替換為下面的迴圈:
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
else
{
// 完成某些工作的其他行程式
}
}
return msg.wParam ;
注意,WM_QUIT訊息被另外挑出來檢查。在普通的訊息迴圈中您不必這么作,因為如果GetMessage接收到一個WM_QUIT訊息,它將傳回0,但是PeekMessage用它的傳回值來指示是否得到一個訊息,所以需要對WM_QUIT進行檢查。
如果PeekMessage的傳回值為TRUE,則訊息按通常方式進行處理。如果傳回值為FALSE,則在將控制傳回給Windows之前,還可以作一點工作(如顯示另一個隨機矩形)。
(儘管Windows檔案上說,您不能用PeekMessage從訊息佇列中刪除WM_PAINT訊息,但是這並不是什麼大不了的問題。畢竟,GetMessage並不從訊息佇列中刪除WM_PAINT訊息。從佇列中刪除WM_PAINT訊息的唯一方法是令視窗顯示區域的失效區域變得有效,這可以用ValidateRect和ValidateRgn或者BeginPaint和EndPaint對來完成。如果您在使用PeekMessage從佇列中取出WM_PAINT訊息後,同平常一樣處理它,那么就不會有問題了。所不能作的是使用如下所示的程式碼來清除訊息佇列中的所有訊息:
while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;
這行敘述從訊息佇列中刪除WM_PAINT之外的所有訊息。如果佇列中有一個WM_PAINT訊息,程式就會永遠地陷在while迴圈中。)
PeekMessage在Windows的早期版本中比在Windows 98中要重要得多。這是因為Windows的16位元版本使用的是非優先權式的多工(我將在第二十章中討論這一點)。Windows的Terminal程式在從通訊埠接收輸入後,使用一個PeekMessage迴圈。列印管理器程式使用這個技術來進行列印,其他的Windows列印套用程式通常都會使用一個PeekMessage迴圈。