2010年9月24日 星期五

Invalidate function 的注意事項

MFC 的 CWnd 中有個 member function 叫作 Invalidate();


void Invalidate(BOOL bErase = TRUE);

這支 member function 可以用來強制某個視窗必須進行重繪,
即是將該視窗設為無效的繪圖區,例如某個視窗的右邊被上一層視窗蓋住,
這右邊就是無效區,在下次被調回前景時就會重繪無效區,即重繪整個畫面。

剛開始寫視窗程式時因為有一個功能必須每 50ms 更新一次畫面的某個部份,
功能上製作正常,但程式在執行時 Client Area 卻閃個不停,
後來才知道還有一支 member function 叫 InvalidateRect();
或可以指定 Region 陣列的 InvalidateRgn();


void InvalidateRect(LPCRECT lpRect, BOOL bErase = TRUE);
void InvalidateRgn(CRgn* pRgn, BOOL bErase = TRUE);


InvalidateRect() 多了一個 Rect 參數可以指定重繪的區域,不必整個重繪,
這樣可以讓不必要更改畫面的地方免除閃爍的情況。

在 Windows API 中相對應的是這支,第一個參數為視窗的 Handle


BOOL InvalidateRect(HWND hWnd, CONST RECT *lpRect, BOOL bErase);
BOOL InvalidateRgn(HWND hWnd, HRGN hRgn, BOOL bErase);


但是 50ms 更新一次也是蠻高的頻率了,大部份的畫面不閃,指定的 Rect 內還是閃不停,

解法是注意看 Invalidate() 的原型有一個預設參數 BOOL bErase = TRUE
原來這個 bErase 控制著函式要不要清空原本的畫面,
清除畫面時會造成背景重繪 (WM_ERASEBKGND) 使 Client Area 閃爍,
所以若強制重繪視窗時,若沒有必要把畫面清空就記得下 Invalidate(FALSE);
如此便會直接畫上新的圖,而節省清空原畫面的時間更減少閃爍的狀況,
什麼時後可以不清除原畫面?舉例來說例如你要畫一個等待中的動畫,
隨著載入進度依順時針方向逐漸畫出一個圓型,這就不需要擦掉已經畫完的部份圓,
再例如股市的曲線圖照常理來說不可能倒縮,只會愈畫愈右邊,
這種情況也不必要擦掉左邊已畫好的交易曲線圖,
注意是否重繪加上適當的使用 InvalidateRect 僅重繪必要的區域,
就可以減少許多閃爍的狀況。

InvalidateRect 是使視窗的某部份無效,而無效的視窗就會被進行重繪,
相對應的 function 還有以下幾個,它們會使視窗的某部份有效,
使某部份成為有效可以達到在 WM_PAINT 時不會被重繪的效果,特殊狀況會用到。


// CWnd
void ValidateRect(LPCRECT lpRect);
void ValidateRgn(CRgn* pRgn);

// Windows API
BOOL ValidateRect(HWND hWnd, CONST RECT *lpRect);
BOOL ValidateRgn(HWND hWnd, HRGN hRgn);


此外需注意的是 Invalidate() 會將區域標示為無效,
等到視窗收到 WM_PAINT 時就會重繪無效區域,
但它的優先權並不高,若在使用 Invalidate() 後還有更重要的指令待處理,
WM_PAINT 訊息就會被延後送出導致重繪慢一拍,
必要立即更新的情況可以在 Invalidate() 後再使用 UpdateWindow();
適時使用 UpdateWindow(); 可以讓程式立即重繪得到正確的畫面。

沒有留言:

張貼留言