繼續WTL的學習,在第二部分的WTL Clock程式中,當定時器拋出WM_TIMER訊息時,處理器OnTimer做了這樣的工作:
LRESULT OnTimer(UINT_PTR nIDEvent)<br />{<br />if(1!=nIDEvent)<br />SetMsgHandled(false);<br />else<br />{<br />GetLocalTime(&m_stLastTime);//儲存目前時間<br />RedrawWindow();//重繪視窗<br />}<br />return 0;<br />}
而用來更新時間的處理器OnPaint由WM_PAINT訊息激發,顯然WM_PAINT是由RedrawWindow()拋出的。
如果直接用PostMessage(WM_PAINT)來代替RedrwaWindow(),編譯運行,發現視窗顯示的時間沒能如願的每隔1Sec更新。為什嗎?明明手動拋出了WM_PAINT訊息,按理說,它應該激發OnPaint函數來處理這個訊息嘛。
F12查看RedrawWindow()代碼,如下:
BOOL RedrawWindow(<br />_In_opt_ LPCRECT lpRectUpdate = NULL,<br />_In_opt_ HRGN hRgnUpdate = NULL,<br />_In_ UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE) throw()<br />{<br />ATLASSERT(::IsWindow(m_hWnd));<br />return ::RedrawWindow(m_hWnd, lpRectUpdate, hRgnUpdate, flags);<br />}
很明顯它是對Platform SDK函數RedrawWindow的簡單封裝,通過查看MSDN,容易瞭解到RedrawWindow函數幹了兩件事:
- 對視窗進行“Invalidate”,也就是聲明待更新地區“到期”
- 拋出WM_NCPAINT,WM_ERASEBKGND,WM_PAINT等訊息
好了,瞭解RedrawWindow足夠多了。我們發現,在WTL Clock程式中,我們只需要WM_PAINT訊息,但實驗證明PostMessage(WM_PAINT)起不了作用,它取代不了RedrawWindow函數。我們唯一漏掉的就是視窗的“Invalidate”(到期聲明)。在MSDN中搜尋Invalidate,剛好得到一個CWindowImpl::Invalidate()成員函數,而WTLClockView類剛好繼承於CWindowImpl,所以在它內部可以直接調用Invalidate()的。
進一步查看Invalidate()的說明,有這樣一句話:“Invalidates the entire client area. Passes NULL for the RECT parameter to the InvalidateRect Win32 function”,意思就是說函數將會聲明整個客戶區到期,而本例中view正是客戶區。另外,它是對Win32函數的封裝,查看InvalidateRect的說明,再連結到Invalidating
the Client Area的說明,第一句話是:“The system is not the only source ofWM_PAINT messages. TheInvalidateRect
orInvalidateRgn function can indirectly generate WM_PAINT messages for your windows.” 它告訴我們系統不是WM_PAINT訊息的唯一來源,函數InvalidateRect或InvalidateRgn同樣可以直接為你的視窗產生WM_PAINT訊息。看來,Invalidate()已經完全滿足本程式的要求了:聲明客戶區(本例中的view地區)“到期”,並拋出了我們需要的WM_PAINT訊息,這將驅動OnPaint()對時間進行更新。所以OnTimer()函數可以修改為:
OnTimer(UINT_PTR nIDEvent)<br />{<br />if(1!=nIDEvent)<br />SetMsgHandled(false);<br />else<br />{<br />GetLocalTime(&m_stLastTime);<br />Invalidate();<br />//PostMessage(WM_NCPAINT);<br />//PostMessage(WM_ERASEBKGND);<br />//PostMessage(WM_PAINT);<br />//RedrawWindow();<br />}<br />return 0;<br />}
連PostMessage(WM_PAINT)都不用寫。編譯運行,結果正如所需要的那樣。
P.S:
Windows規範是禁止使用者主動拋WM_PAINT訊息的,WM_PAINT訊息應該由InvalidateRect等函數來激發。只要客戶區存在無效地區,訊息佇列中就包含一條WM_PAINT訊息,但該訊息的優先順序很低,只有在沒有其它訊息的情況下才被GetMessage函數抓取到。多條WM_PAINT訊息通過綜合無效地區合并為一條。API函數RedrawWindow比Invalidate功能強大得多,二者有很大的不同,但在本例中它們都用於無效化整個客戶區使之重繪。區別在於本例中的RedrawWindow是立馬進行繪製,而Invalidate引起的WM_PAINT何時被處理是不確定的。(2013-06-22)