【转】VC窗口刷新InvalidateRect和UpdateWindow RedrawWindow
The WM_PAINT message is generated by the system and should not be sent by an application.The system sends this message when there are no other messages in the application's message queue
也就是說(shuō)WM_PAINT消息是由系統(tǒng)產(chǎn)生,非要等應(yīng)用程序的消息隊(duì)列為空時(shí)才發(fā)送WM_PAINT消息,并且 該消息不應(yīng)該被程序(自己寫(xiě)代碼用SendMessage)來(lái)發(fā)送。
當(dāng)調(diào)用UpdateWindow函數(shù),或者是Window檢測(cè)到 窗口被覆蓋的地方需要恢復(fù)的時(shí)候,比如,第一次創(chuàng)建窗口,改變了窗口的大小,最大化,最小化等等(其實(shí)這些事件發(fā)生時(shí)會(huì)調(diào)用UpdateWindow函 數(shù),由該函數(shù)發(fā)送WM_PAINT消息),它會(huì)向用戶(hù)程序發(fā)送一個(gè)WM_PAINT消息。窗口過(guò)程收到WM_PAINT消息后,并不代表整個(gè)客戶(hù)區(qū)都需要 被刷新,有可能客戶(hù)區(qū)被覆蓋的區(qū)域只有一小塊,這個(gè)區(qū)域叫做“無(wú)效區(qū)域”,程序只需要更新這個(gè)區(qū)域。與WM_TIMER消息類(lèi)似,WM_PAINT消息也 是一個(gè)低級(jí)別的消息,雖然它不會(huì)像WM_TIMER消息一樣被丟棄,但Windows總是在消息循環(huán)空的時(shí)候才把WM_PAINT放入其中。
無(wú)效區(qū)域的坐標(biāo)并不附帶在WM_PAINT消息的參數(shù)中,在程序中有其他方法可以獲取。WM_PAINT消息只 是通知程序有個(gè)區(qū)域需要更新而已,所以Windows也不會(huì)同時(shí)將兩條WM_PAINT消息放入消息循環(huán)中。當(dāng)Windows要放入一條WM_PAINT 消息的時(shí)候,如果發(fā)現(xiàn)已經(jīng)存在一個(gè)無(wú)效區(qū)域了,那么它只需要把新舊兩個(gè)無(wú)效區(qū)域合并計(jì)算出一個(gè)無(wú)效區(qū)域就可以了,消息循環(huán)中還是只需要一條 WM_PAINT消息。
實(shí)際上,Windows為每個(gè)窗口維護(hù)一個(gè)“繪圖信息結(jié)構(gòu)”,無(wú)效區(qū)域的坐標(biāo)就在其中,每當(dāng)消息循環(huán)空的時(shí)候, 如果Windows發(fā)現(xiàn)存在一個(gè)無(wú)效區(qū)域,就會(huì)放入一個(gè)WM_PAINT消息。那么“繪圖信息結(jié)構(gòu)”怎么獲取呢?BeginPaint函數(shù)的第二個(gè)參數(shù)就 是一個(gè)繪圖信息結(jié)構(gòu)的緩沖區(qū)地址,windows會(huì)在這里返回繪圖信息結(jié)構(gòu),結(jié)構(gòu)中包含了無(wú)效區(qū)域的位置和大小,繪圖信息結(jié)構(gòu)的定義如下:
typedef struct tagPAINTSTRUCT { // ps
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
其中hdc字段是窗口的設(shè)備環(huán)境句柄,rcPaint字段是一個(gè)RECT結(jié)構(gòu),它指定了無(wú)效區(qū)域矩形的對(duì)角頂點(diǎn) (如果開(kāi)始有一個(gè)((0,0),(40,40)),現(xiàn)在又來(lái)一個(gè)((20,20),(60,30)),那么拼接后就是((0,0), (60,40))),fErase字段如果為非零值,表示W(wǎng)indows在發(fā)送WM_PAINT消息前已經(jīng)使用背景色擦除了無(wú)效區(qū)域,后面3個(gè)字段是 Windows內(nèi)部使用的,應(yīng)用程序不必去理會(huì)他們。
在某些情況下,顯示區(qū)域的一部分被臨時(shí)覆蓋,Windows試圖 保存一個(gè)顯示區(qū)域,并在以后恢復(fù)它,但這不一定能成功。Windows可能發(fā)送WM_PAINT消息:Windows 擦除覆蓋了部分窗口的對(duì)話(huà)框或消息框;菜單下拉出來(lái),然后被釋放;顯示工具提示消息。
在某些情況下,Windows總是一定保存它所覆蓋的顯示區(qū)域,然后恢復(fù)它。這些情況是:鼠標(biāo)光標(biāo)穿越顯示區(qū)域;圖標(biāo)拖過(guò)顯示區(qū)域。
有時(shí)候應(yīng)用也需要能夠主動(dòng)引發(fā)窗口中的繪制操作,比如當(dāng)窗口顯示的數(shù)據(jù)改變的時(shí)候,這一般是通過(guò) InvalidateRect和 InvalidateRgn函數(shù)來(lái)完成的。InvalidateRect和InvalidateRgn把指定的區(qū)域加到窗口的Update Region中,當(dāng)應(yīng)用的消息隊(duì)列沒(méi)有其他消息時(shí),如果窗口的Update Region不為空時(shí),系統(tǒng)就會(huì)自動(dòng)產(chǎn)生WM_PAINT消息。
系統(tǒng)為什么不在調(diào)用Invalidate時(shí)發(fā)送WM_PAINT消息呢?又為什么非要等應(yīng)用消息隊(duì)列為空時(shí)才發(fā) 送WM_PAINT消息呢?這是因?yàn)橄到y(tǒng)把在窗口中的繪制操作當(dāng)作一種低優(yōu)先級(jí)的操作,于是盡 可能地推后做。不過(guò)這樣也有利于提高繪制的效率:兩個(gè)WM_PAINT消息之間通過(guò)InvalidateRect和InvaliateRgn使之失效的區(qū) 域就會(huì)被累加起來(lái),然后在一個(gè)WM_PAINT消息中一次得到 更新,不僅能避免多次重復(fù)地更新同一區(qū)域,也優(yōu)化了應(yīng)用的更新操作。像這種通過(guò)InvalidateRect和InvalidateRgn來(lái)使窗口區(qū)域無(wú) 效,依賴(lài)于系統(tǒng)在合適的時(shí)機(jī)發(fā)送WM_PAINT消息的機(jī) 制實(shí)際上是一種異步工作方式,也就是說(shuō),在無(wú)效化窗口區(qū)域和發(fā)送WM_PAINT消息之間是有延遲的;有時(shí)候這種延遲并不是我們希望的,這時(shí)我們當(dāng)然可以 在無(wú)效化窗口區(qū)域后利用SendMessage 發(fā)送一條WM_PAINT消息來(lái)強(qiáng)制立即重畫(huà),但不如使用Windows GDI為我們提供的更方便和強(qiáng)大的函數(shù):UpdateWindow和RedrawWindow。UpdateWindow會(huì)檢查窗口的Update Region,當(dāng)其不為空時(shí)才發(fā)送WM_PAINT消息;RedrawWindow則給我們更多的控制:是否重畫(huà)非客戶(hù)區(qū)和背景,是否總是發(fā)送 WM_PAINT消息而不管Update Region是否為空等。
BeginPaint
今天在處理WM_PAINT消息時(shí)產(chǎn)生了一個(gè)低級(jí)的錯(cuò)誤,并搞的我花了快一個(gè)小時(shí)才找到原因。我在處理消息時(shí), 沒(méi)有使用BeginPaint和EndPaint這對(duì)函數(shù),結(jié)果我其余的消息彈不出來(lái),窗口拖動(dòng)時(shí),不停閃爍(其實(shí)那就是重繪)。后來(lái)還是在MSDN上找 到了答案,現(xiàn)將原話(huà)貼出來(lái)。(在MSDN的The WM_PAINT Message標(biāo)題中)
BeginPaint sets the update region of a window to NULL. This clears the region, preventing it fromgenerating subsequent WM_PAINT messages. If an application processes a WM_PAINT message but does not call BeginPaint or otherwise clear the update region, the application continues to receive WM_PAINT messages as long as the region is not empty. In all cases, an application must clear the update region before returning from the WM_PAINT message.
BeginPaint函數(shù)的作用就是將窗口需要重繪的區(qū)域設(shè)置為空(也就是Update Region置空)。在正常情況下,我們接收到了WM_PAINT消息后,窗口的Update Region都是非空的(如果為空就不需要發(fā)送WM_PAINT消息了)。而當(dāng)你響應(yīng)這個(gè)消息的時(shí)候又不調(diào)用BeginPaint來(lái)清空,窗口的 Update Region就一直是非空的,系統(tǒng)就會(huì)一直發(fā)送WM_PAINT消息。這樣就形成了一個(gè)處理WM_PAINT消息的死循環(huán)。
BeginPaint和WM_PAINT消息緊密相關(guān)。試一試在WM_PAINT處理函數(shù)中不寫(xiě) BeginPaint會(huì)怎樣?程序會(huì)像進(jìn)入了一個(gè)死循環(huán)一樣達(dá)到驚人的CPU占用率,你會(huì)發(fā)現(xiàn)程序總在處理一個(gè)接 一個(gè)的WM_PAINT消息。這是因?yàn)樵谕ǔG闆r下,當(dāng)應(yīng)用收到WM_PAINT消息時(shí),窗口的Update Region都是非空的(如果為空就不需要發(fā)送WM_PAINT消息了),BeginPaint的一個(gè)作用就是把該Update Region置為空,這樣如果不調(diào)用BeginPaint,窗口的Update Region就一直不為空,如前所述,系統(tǒng)就會(huì)一直發(fā)送WM_PAINT消息。
BeginPaint和WM_ERASEBKGND消息也有關(guān)系。當(dāng)窗口的Update Region被標(biāo)志為需要擦除背景時(shí),BeginPaint會(huì)發(fā)送WM_ERASEBKGND消息來(lái)重畫(huà)背景,同時(shí)在其返回信息里有一個(gè)標(biāo)志表明窗口背景 是否被重畫(huà)過(guò)。當(dāng)我們用InvalidateRect和InvalidateRgn來(lái)把指定區(qū)域加到Update Region中時(shí),可以設(shè)置該區(qū)域是否需要被擦除背景,這樣下一個(gè)BeginPaint就知道是否需要發(fā)送WM_ERASEBKGND消息了。
當(dāng)然關(guān)于 WM_PAINT消息還有很多的知識(shí)需要學(xué)習(xí)。另外要注意的一點(diǎn)是,BeginPaint只能在WM_PAINT處理函數(shù)中使用,并且在調(diào)用了 BeginPaint函數(shù)后,不要忘記了調(diào)用EndPaint函數(shù),他們可是一對(duì)的。
重畫(huà) 函數(shù) InvalidateRect,Invalidate,UpdateWindow, RedrawWindow
InvalidateRect(部分區(qū)域) 和Invalidate(整個(gè)窗口) 僅僅是用來(lái)設(shè)置無(wú)效區(qū)域,但是并不重繪窗口。
UpdateWindow 檢查窗口有無(wú)無(wú)效區(qū)域,如果有,則立即發(fā)送一個(gè)WM_PAINT 消息給窗口并立即重畫(huà)。
RedrawWindow相當(dāng)于先調(diào)用InvalidateRect,緊接著又調(diào)用UpdateWindow, 此外RedrawWindow還提供了一些前兩者沒(méi)法做到的功能。
????? 如果不調(diào)用 InvalidateRect就調(diào)用 UpdateWindow,那么UpdateWindow什么都不做,因?yàn)闆](méi)有無(wú)效區(qū)域。如果調(diào)用 InvalidateRect 后不調(diào)用UpdateWindow,則系統(tǒng)會(huì)自動(dòng)在窗口消息隊(duì)列為空的時(shí)候,系統(tǒng)自動(dòng)發(fā)送一WM_PAINT消息。
與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的【转】VC窗口刷新InvalidateRect和UpdateWindow RedrawWindow的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 脚手架 - ref
- 下一篇: 如何设置窗口立即刷新显示