OnPaint()與OnDraw()的區別:
OnPaint是WM_PAINT的消息響應函數,在MFC的基類里OnPaint函數調用了OnDraw()函數。
OnPaint函數另外還調用了OnPrepareDC()函數。
如果在窗口子類覆蓋了OnPaint函數,當MFC調用我們重寫的OnPaint函數時,就調不到OnDraw()函數了,
除非我們去調用OnDraw()函數。
Invalidate函數族介紹:
函數: Invalidate(BOOL bErase = TRUE)
函數: InvalidateRect(CRect* rect,BOOL bErase = TRUE)
函數: InvalidateRgn(CRgn* rgn,BOOL bErase = TRUE)
將一個區域放入Update Region中。[Update
Region]是窗口的無效區域。[無效區域]是需要重繪的區域。
為何需要重繪呢?
第1類事件:當需要展現某窗口的“新的”區域時,就需要重繪。
當創建一個窗口時;當把窗口從另一個窗口的背后彈到前面時;當從圖標化到最大化轉變時;
當滾動式的窗口,發生滾動事件時;當把遮擋在前面的窗口一點一點拖開,
讓被遮擋的窗口一點一點的露出時
就需要重繪。這些動作都是由WINDOWS系統管理的,系統會很肯定的認為,在上述事件發生時,必須重繪。
注:如果把被遮擋的窗口,一點一點的遮蓋住,就不需要重繪。
第2類事件:當有業務數據改變的事件發生時。
窗口是用來顯示業務數據的。比如我的窗口正在顯示一個橢圓,后臺將業務數據變成
了三角形,我需要顯示這個三角形,這時就需要重繪了。對于第2類事件,
WINDOWS不可能感知到你需要重繪。
例如:我有一個變量 int m_shape=1; 1代表橢圓,2代表三角形。
我需要讓窗口的圖形顯示m_shape代表的形狀
當我把m_shape的值由1變成2時,WINDOWS根本不知道我需要重繪一個三角形。
對于第1類事件,WINDOWS會自動發出WM_PAINT消息,窗口的對應處理函數OnPaint()就會被調用。
程序員不必關心“在何時”和“在何地”重繪。對于第2類事件,程序員必須通知WINDOWS,在何地重繪。
至于“何時”重繪,WINDOWS會挑選一個合適的時機。
Invalidate函數族同第2類事件有關。通過調用Invalidate函數族,通知windows系統,
我有一些窗口區域需要重繪。CWnd::Invalidate()是說整個窗口都需要重繪。
CWnd::InvalidateRect()是說窗口的某個矩形區域需要
重繪。CWnd::InvalidateRgn是說窗口的某個不規則區域需要重繪。
“不規則區域”可以是任意多邊形,橢圓形,當然也包括矩形。
用偽代碼說明上述三個函數的等價關系。
CRgn rgn;
rgn.CreateRectRgn(...);
CWnd::InvalidateRgn(&rgn,...);
等價于
CRect rect;
CWnd::InvalidateRect(&rect,...);
CRect rect;
GetClientRect(&rect);
CWnd::InvalidateRect(&rect,...);
等價于
CWnd::Invalidate(...);
Invalidate函數族中,都有一個bErase參數。此參數的含義:bErase=TRUE擦除背景,
bErase=FALSE不擦除背景
何為背景:想象窗口就是小朋友的畫紙。當你把這張畫紙給另一個小朋友畫畫時,
前一個小朋友畫的東西就是
“背景”。一般我們不希望兩個小朋友畫的東西夾雜在一起。我們就需要擦除前一個小朋友畫的“背景”。
用什么擦除背景呢?WINDOWS允許我們設置“背景刷”,就是用某種顏色的刷子把整個畫紙涂抹一遍,
有點像刷白墻。
Invalidate函數族的調用不會立刻引發窗口重繪。Invalidate函數族只是累積和標記需要重繪的區域。
下一次"WM_PAINT message
occurs"時(MSDN語),一次性處理累積和標記的所有需要重繪的區域。顯然從
Invalidate調用,到實際的重繪動作是異步調用的。人類視覺有延遲現象,一秒連續播放24幀就可以認為是
“動畫”了,所以上述重繪方式人類是察覺不出異樣的。假設每次Invalidate都同步的引發重繪OnPaint,有兩個不良后果:一是程序效率太差,二是可能讓人察覺出閃爍感。
那么何時下一次"WM_PAINT message occurs"呢?
當應用的消息隊列沒有其他消息時,并且窗口的[Update Region]不為空時,
系統就會自動產生WM_PAINT消息。
例子:演示“失效區域”是如何起作用的。
//每次重繪,會交替展現兩個不同的橢圓形。
void XXX::OnPaint()
{
CPaintDC dc(this);
static
int x=0;
if
(x==0){
dc.Ellipse (0, 0, 100, 200);
//橫向的橢圓形
x=1;
}
else{
dc.Ellipse (0, 0, 200, 100);
//豎向的橢圓形
x=0;
}
}
某CButton中,OnBnClicked偽代碼:
CRect rect;
XXX->GetClientRect(&rect);
rect.bottom = rect.bottom/2;
XXX->InvalidateRect(&rect,true);
//擦除背景
即使我擦除了背景,仍舊能看到前一個橢圓。因為我設定的“非法區域”只是rect的上半部分。
CRect rect;
XXX->GetClientRect(&rect);
XXX->InvalidateRect(&rect,true);
//擦除背景
可以正常的展現,能交替展現兩個不同的橢圓形。
Validate函數族:
作用同Invalidate函數族相反,將一個區域從[Update Region]排除,這樣就不會被重繪。
當然了,Validate要在下一次"WM_PAINT message occurs"之前的調用才能起作用。
如果發生了第1類事件,會造成大面積的區域變成“需重繪區域”,Validate設定的“不需重繪區域”
又會變成“需重繪區域了”。
UpdateWindow函數:
UpdateWindow會檢查窗口的Update Region,當其不為空時才發送WM_PAINT消息。
UpdateWindow可以繞開應用程序消息循環,直接發送WM_PAINT消息給窗口。
RedrawWindows函數:
可以簡單理解為Invlidate + UpdateWindow,但是功能更強大一些。
SetRedraw函數:
可以阻止窗口重繪。是解決窗口閃爍的一個辦法
MSDN的一個例子:
m_List.SetRedraw(FALSE); //暫時阻止窗口m_List重繪
...//大規模對m_List改頭換面
m_List.SetRedraw(TRUE);
//解除阻止窗口m_List重繪
m_List.Invalidate();
m_List.UpdateWindow(); ?//觸發WM_PAINT消息
SetRedraw函數好像是戲臺的前幕,后面切換場景時,先遮擋一下。
<>介紹了圖形密集型程序“閃”的原因。
主要技術為:1 選用黑色背景或者背景同前景相近的顏色,作為背景刷。
2 雙緩沖技術,就是先在內存設備DC里準備好需要顯示的內容,然后拷貝到屏幕設備DC
3 剪裁區域的合理利用。