視窗重新整理問題(WMPAINT、BeginPaint、EndPaint)
在某些情況下,顯示區域的一部分被臨時覆蓋,Windows試圖儲存一個顯示區域,並在以後恢復它,但這不一定能成功。在以下情況下,Windows可能傳送WM_PAINT訊息:
Windows擦除覆蓋了部分視窗的對話方塊或訊息框。
選單下拉出來,然後被釋放。
顯示工具提示訊息。
在某些情況下,Windows總是儲存它所覆蓋的顯示區域,然後恢復它。這些情況是:
滑鼠游標穿越顯示區域。
圖示拖過顯示區域。
處理WM_PAINT訊息要求程式寫作者改變自己向顯示器輸出的思維方式。程式應該組織成可以保留繪製顯示區域需要的所有資訊,並且僅當「響應要求」- 即Windows給視窗訊息處理程式傳送WM_PAINT訊息時才進行繪製。如果程式在其它時間需要更新其顯示區域,它可以強制Windows產生一個 WM_PAINT訊息。這看來似乎是在螢幕上顯示內容的一種捨近求遠的方法。但您的程式結構可以從中受益。
1、系統何時傳送WM_PAINT訊息?
系統會在多個不同的時機發送WM_PAINT訊息:當第一次建立一個視窗時,當改變視窗的大小時,當把視窗從另一個視窗背後移出時,當最大化或最小化窗 口時,等等,這些動作都是由 系統管理的,應用只是被動地接收該訊息,在訊息處理函式中進行繪製操作;大多數的時候應用也需要能夠主動引發視窗中的繪製操作,比如當視窗顯示的資料改變 的時候,這一般是通過InvalidateRect和 InvalidateRgn函式來完成的。InvalidateRect和InvalidateRgn把指定的區域加到視窗的Update Region中,當應用的訊息佇列沒有其他訊息時,如果視窗的Update
Region不為空時,系統就會自動產生WM_PAINT訊息。
系統為什麼不在呼叫Invalidate時傳送WM_PAINT訊息呢?又為什麼非要等應用訊息佇列為空時才傳送WM_PAINT訊息呢?這是因為系統 把在視窗中的繪製操作當作一種低優先順序的操作,於是盡 可能地推後做。不過這樣也有利於提高繪製的效率:兩個WM_PAINT訊息之間通過InvalidateRect和InvaliateRgn使之失效的區 域就會被累加起來,然後在一個WM_PAINT訊息中一次得到 更新,不僅能避免多次重複地更新同一區域,也優化了應用的更新操作。像這種通過InvalidateRect和InvalidateRgn來使視窗區域無
效,依賴於系統在合適的時機發送WM_PAINT訊息的機 制實際上是一種非同步工作方式,也就是說,在無效化視窗區域和傳送WM_PAINT訊息之間是有延遲的;有時候這種延遲並不是我們希望的,這時我們當然可以 在無效化視窗區域後利用SendMessage 傳送一條WM_PAINT訊息來強制立即重畫,但不如使用Windows GDI為我們提供的更方便和強大的函式:UpdateWindow和RedrawWindow。UpdateWindow會檢查視窗的Update Region,當其不為空時才傳送WM_PAINT訊息;RedrawWindow則給我們更多的控制:是否重畫非客戶區和背景,是否總是傳送
WM_PAINT訊息而不管Update Region是否為空等。
2、BeginPaint
今天在處理WM_PAINT訊息時產生了一個低階的錯誤,並搞的我花了快一個小時才找到原因。我在處理訊息時,沒有使用BeginPaint和 EndPaint這對函式,結果我其餘的訊息彈不出來,視窗拖動時,不停閃爍(其實那就是重繪)。後來還是在MSDN上找到了答案,現將原話貼出來。(在 MSDN的The WM_PAINT Message標題中)
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函式的作用就是將視窗需要重繪的區域設定為空(也就是Update Region置空)。在正常情況下,我們接收到了WM_PAINT訊息後,視窗的Update Region都是非空的(如果為空就不需要傳送WM_PAINT訊息了)。而當你響應這個訊息的時候又不呼叫BeginPaint來清空,視窗的 Update Region就一直是非空的,系統就會一直髮送WM_PAINT訊息。這樣就形成了一個處理WM_PAINT訊息的死迴圈。這就是我出現錯誤的原因,低階 錯誤。
BeginPaint和WM_PAINT訊息緊密相關。試一試在WM_PAINT處理函式中不寫BeginPaint會怎樣?程式會像進入了一個死迴圈 一樣達到驚人的CPU佔用率,你會發現程式總在處理一個接 一個的WM_PAINT訊息。這是因為在通常情況下,當應用收到WM_PAINT訊息時,視窗的Update Region都是非空的(如果為空就不需要傳送WM_PAINT訊息了),BeginPaint的一個作用就是把該Update Region置為空,這樣如果不呼叫BeginPaint,視窗的Update Region就一直不為空,如前所述,系統就會一直髮送WM_PAINT訊息。
BeginPaint和WM_ERASEBKGND訊息也有關係。當視窗的Update Region被標誌為需要擦除背景時,BeginPaint會發送WM_ERASEBKGND訊息來重畫背景,同時在其返回資訊裡有一個標誌表明視窗背景 是否被重畫過。當我們用InvalidateRect和InvalidateRgn來把指定區域加到Update Region中時,可以設定該區域是否需要被擦除背景,這樣下一個BeginPaint就知道是否需要傳送WM_ERASEBKGND訊息了。
當然關於 WM_PAINT訊息還有很多的知識需要學習。另外要注意的一點是,BeginPaint只能在WM_PAINT處理函式中使用,並且在呼叫了BeginPaint函式後,不要忘記了呼叫EndPaint函式,他們可是一對的。
3、重畫函式 InvalidateRect,UpdateWindow, RedrawWindow的區別
InvalidateRect是通過執行緒的訊息佇列來發送重新整理訊息,是最常用的。
UpdateWindow是直接呼叫視窗函式立即響應重新整理訊息,使視窗重新整理訊息優先被響應(訊息佇列中如果沒有WM_PAINT訊息就什麼都不執行),一般是在ShowWindow之後呼叫。
RedrawWindow相當於先呼叫InvalidateRect,緊接著又呼叫UpdateWindow,此外RedrawWindow還提供了一些前兩者沒法做到的功能。
補充幾點:
1.WM_Paint 是一個被動訊息,不能通過普通的方法簡單的 sendmessage WM_paint 了事這是不行的;但通過訊息由程式設計師引發不是不可能;通過幾個特殊的常數可以做到,不過要到delphi下找sendmessage 可以將訊息傳送到訊息佇列;但windows會自動判斷是否存在無效的畫圖區域;如果存在無效的畫圖區域,則可能會重畫,反之則棄用該訊息.可以使用 InvalidateRect 等幾個APi將螢幕上任意一個個矩形區域設定為無效區域,在UpdateWindow後呼叫後,windows會自動查詢是否存在無效,並重畫,該矩形區
域;
BeginPaint和GetDC的區別
這是個windows程式設計問題。
第一種情況顯示出來的字很正常。
-
case WM_PAINT:
-
gdc = BeginPaint (hwnd, &ps);
-
TextOut (gdc,
0,
0, s,
strlen (s));
-
EndPaint (hwnd, &ps);
break;
第二種情況顯示的字不停閃爍。
-
case WM_PAINT:
-
gdc = GetDC (hwnd);
-
TextOut (gdc,
0,
0, s,
strlen (s));
-
ReleaseDC (hwnd, gdc);
-
break;
請教兩種函式的作用?
BeginPaint() 和EndPaint() 可以刪除訊息佇列中的WM_PAINT訊息,並使無效區域有效。
GetDC()和ReleaseDC()並不刪除也不能使無效區域有效,因此當程式跳出 WM_PAINT 時 ,無效區域仍然存在。系統就回不斷髮送WM_PAINT訊息,於是程式不斷處理WM_PAINT訊息。
相 當於BeginPaint、EndPaint會告訴GDI內部,這個視窗需要重畫的地方已經重畫了,這樣WM_PAINT處理完返回給系統後,系統不會再 重發WM_PAINT,而GetDC沒有告訴系統這個視窗需要重畫的地方已經畫過,在你把程式返回給系統後,系統一直以為通知你的重畫命令你還沒有乖乖的 執行或者執行出錯,所以在訊息空閒時,它還會不斷地發WM_PAINT催促你畫,導致程式卡死。
無效區域 :
無效區域就是指需要重畫的區域,無效的意思是:當前內容是舊的,過時的。
假設A是新彈出的一個對話方塊或被啟用的現有對話方塊,A對話方塊置於原來的活動對話方塊B前面,造成對話方塊B的部分或全部被覆蓋,當對話方塊A移開或關閉後,使對話方塊B原來被覆蓋的地方重新可見。那部分被覆蓋的地方就稱為無效區域。
只 有當一個視窗訊息空閒時,系統才會抽空檢查一下這個視窗的無效區域是否為非空(WM_PAINT的優先順序是最低的。這也就是為什麼系統很忙時視窗和桌面往 往會出現變白、重新整理不了、留拖拽痕跡等現象的原因),如果非空,系統就傳送WM_PAINT。所以一定要用BeginPaint、EndPaint把無效 區域設為空,否則WM_PAINT將一直被髮送。
為什麼WINDOWS要提出無效區域的概念?
這是為了加速。
因為BeginPaint和EndPaint用到的裝置描述符只會在當前的無效區域內繪畫,在有效區域內的繪畫會自動被過濾,大家都知道,WIN GDI的繪畫速度是比較慢的,所以能節省一個象素就節省一個,不用吝嗇,這樣可以有效加快繪畫速度。
可見BeginPaint、EndPaint是比較“被動”的,只在視窗新建時和被摧殘時才重畫。
而 GetDC用於主動繪製,只要你指到哪,它就打到哪。它不加判斷就都畫上去,無效區域跟它沒關係。對話方塊沒被覆蓋沒被摧殘,它很健康,系統沒要求它重畫, 但開發者有些情況下需要它主動重畫:比如一個定時換外觀的視窗,這時候就要在WM_TIMER處理程式碼用GetDC。這時候再用BeginPaint、 EndPaint的話,會因為無效區域為空,所有繪畫操作都將被過濾掉。
eg:
-
PAINTSTRUCT ps;
-
HDC hdc = BeginPaint(hWnd,&ps);
-
//Create a DC that matches the device
-
HDC hdcMem = CreateCompatibleDC(hdc);
-
//Load the bitmap
-
HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP,
0,
0,
0);
-
//Select the bitmap into to the compatible device context
-
HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
-
//Get the bitmap dimensions from the bitmap
-
BITMAP bmp;
-
GetObject(hBmp,
sizeof(BITMAP),&bmp);
-
//Get the window area
-
RECT rc;
-
GetClientRect(hWnd,&rc);
-
//Copy the bitmap image from the memory DC to the screen DC
-
BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem,
0,
0,SRCCOPY);
-
//Restore original bitmap selection and destroy the memory DC
-
SelectObject(hdcMem,hOldSel);
-
DeleteDC(hdcMem);
-
EndPaint(hWnd,&ps);
-
return
0;
/////////////////////////
下面是更加詳細的介紹
//
//TITLE:
// EVC繪製點陣圖–BeginPaint()與GetDC()的區別
//AUTHOR:
// norains
//DATE:
// Tuesday 29-August-2006
//
1、BeginPaint()和GetDC()
在EVC中繪製點陣圖比較方便,有不少現成的函式可供呼叫,我們所要注意的只是BeginPaint()或GetDC()的使用即可.
因為程式碼比較簡單,所以不做更多解釋.
這是訊息迴圈函式:
-
LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
-
{
-
…
-
-
switch(wMsg)
-
{
-
case WM_PAINT:
-
OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
-
break;
-
-
…
-
-
}
-
return DefWindowProc(hWnd,wMsg,wParam,lParam);
-
-
…
-
-
}
響應WM_PAINT訊息的函式,在這裡進行點陣圖的繪製:
-
LRESULT OnPaintMainWnd(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
-
{
-
PAINTSTRUCT ps;
-
HDC hdc = BeginPaint(hWnd,&ps);
-
//Create a DC that matches the device
-
HDC hdcMem = CreateCompatibleDC(hdc);
-
//Load the bitmap
-
HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP,
0,
0,
0);
-
//Select the bitmap into to the compatible device context
-
HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
-
//Get the bitmap dimensions from the bitmap
-
BITMAP bmp;
-
GetObject(hBmp,
sizeof(BITMAP),&bmp);
-
//Get the window area
-
RECT rc;
-
GetClientRect(hWnd,&rc);
-
//Copy the bitmap image from the memory DC to the screen DC
-
BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem,
0,
0,SRCCOPY);
-
//Restore original bitmap selection and destroy the memory DC
-
SelectObject(hdcMem,hOldSel);
-
DeleteDC(hdcMem);
-
EndPaint(hWnd,&ps);
-
return
0;
-
}
我們都知道BeginPaint()和EndPaint()需要配套使用,並且這兩個函式也只能用在WM_PAINT訊息的相應函式當中.如果我們在 WM_PAINT的響應函式中將以上兩個繪製函式相應替換為GetDC()和ReleaseDC()會有什麼結果呢?
即:
- HDC hdc = BeginPaint(hWnd,&ps); --> HDC hdc = GetDC(hWnd);
- EndPaint(hWnd,&ps); --> ReleaseDC(hWnd,hdc);
編譯並執行程式,我們發現視窗一片空白,好像沒有繪製點陣圖.但其實不盡然,我們採用單步除錯,可以發現其實點陣圖已經繪製出來,只不過又被背景顏色抹掉了. 由此可知,如果需要使用GetDC(),我們對訊息迴圈函式必須要加上對WM_ERASEBKGND的處理:
-
LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
-
{
-
switch(wMsg)
-
{
-
case WM_PAINT:
-
OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
-
break;
-
case WM_ERASEBKGND
-
return
0;
-
}
-
return DefWindowProc(hWnd,wMsg,wParam,lParam);
-
}
只要系統不對WM_ERASEBKGND進行預設處理,我們用GetDC()替代BeginPaint()就可以正常使用.
至此我們可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的區別.前一對只能用在WM_PAINT響應 函式中,並且繪製背景時不會被抹掉;後一對隨處可用,但如果用在WM_PAINT響應函式中,那麼接下來將會被WM_ERASEBKGND訊息的響應函式 的背景繪製給抹掉。
2、繪圖閃爍問題
有時候我們大量繪製螢幕時,可能會出現螢幕閃爍問題,這時候可以採用雙緩衝的做法.步驟首先是建立一個記憶體DC,然後往記憶體DC中繪圖,最後把記憶體DC的 內容複製到顯示DC中,完成繪製.具體過程並不複雜,結合程式碼來說明一下.
PS:這段程式碼也是相應WM_PAINT 訊息的.
-
PAINTSTRUCT ps;
-
HDC hdc;
-
//獲取螢幕顯示DC
-
hdc = BeginPaint (hWnd, &ps);
-
-
//建立記憶體DC
-
HDC hdcMem = CreateCompatibleDC(hdc);
-
//建立一個bmp記憶體空間
-
HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);
-
//將bmp記憶體空間分配給記憶體DC
-
HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
-
-
//這是使用者需要繪製的畫面,全部往記憶體DC繪製
-
Rectangle(hdcMem,
0,
0,SCREEN_WIDTH,SCREEN_HEIGHT);
-
DrawMenuButton(hdcMem);
-
-
//將記憶體DC的內容複製到螢幕顯示DC中,完成顯示
-
BitBlt(hdc,
0,
0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem,
0,
0,SRCCOPY);
-
//清除資源
-
SelectObject(hdcMem,hOldSel);
-
DeleteDC(hdcMem);
-
EndPaint(hWnd,&ps);
OnPaint是WM_PAINT訊息的訊息處理函式,在OnPaint中呼叫OnDraw,一般來說,使用者自己的繪圖程式碼應放在OnDraw中。
OnPaint()是CWnd的類成員,負責響應WM_PAINT訊息。OnDraw()是CVIEW的成員函式, 沒有響應訊息的功能.當檢視變得無效時(包括大小的改變,移動,被遮蓋等等),Windows傳送WM_PAINT訊息。該檢視的OnPaint 處理函式通過建立CPaintDC類的DC物件來響應該訊息並呼叫檢視的OnDraw成員函式.OnPaint最後也要呼叫OnDraw,因此一般在 OnDraw函式中進行繪製。
The WM_PAINT message is sent when the UpdateWindow or RedrawWindow member function is called.
在OnPaint中,將呼叫BeginPaint,用來獲得客戶區的顯示裝置環境,並以此呼叫GDI函式執行繪圖操作。在繪圖操作完成後,將呼叫EndPaint以釋放顯示裝置環境。而OnDraw在BeginPaint與EndPaint間被呼叫。
1) 在mfc結構裡OnPaint是CWnd的成員函式. OnDraw是CView的成員函式.
2) OnPaint()呼叫OnDraw(),OnPrint也會呼叫OnDraw(),所以OnDraw()是顯示和列印的共同操作。
OnPaint是WM_PAINT訊息引發的重繪訊息處理函式,在OnPaint中會呼叫OnDraw來進行繪圖。OnPaint中首先構造一個 CPaintDC類得例項,然後一這個例項為引數來呼叫虛擬函式OnPrepareDC來進行一些繪製前的一些處理,比設定對映模式,最後呼叫 OnDraw。而OnDraw和OnPrepareDC不是訊息處理函式。所以在不是因為重繪訊息所引發的OnPaint導致OnDraw被呼叫時,比如在OnLButtonDown等訊息處理函式中繪圖時,要先自己呼叫OnPrepareDC。
至於CPaintDC和CClientDC根本是兩回事情,CPaintDC是一個裝置環境類,在OnPaint中作為引數傳遞給OnPrepareDC來作裝置環境的設定。真正和CClientDC具有可比性的是CWindowDC,他們一個是描述客戶區域,一個是描述整個螢幕。
如果是對CVIEW或從CVIEW類派生的視窗繪圖時應該用OnDraw。
OnDraw()和OnPaint()有什麼區別呢?
首先:我們先要明確CView類派生自CWnd類。而OnPaint()是CWnd的類成員,同時負責響應WM_PAINT訊息。OnDraw()是CVIEW的成員函式,並且沒有響應訊息的功能。這就是為什麼你用VC成的程式程式碼時,在檢視類只有OnDraw沒有OnPaint的原因(因為繼承關係被隱藏了啊!!!)。而在基於對話方塊的程式中,只有OnPaint。
其次:我們在第《每天跟我學MFC》3的開始部分已經說到了。要想在螢幕上繪圖或顯示圖形,首先需要建立裝置環境DC。其實DC是一個數據結構,它包含輸出裝置(不單指你17寸的純屏顯示器,還包括印表機之類的輸出裝置)的繪圖屬性的描述。MFC提供了CPaintDC類和CWindwoDC類來實時的響應,而CPaintDC支援重畫。當 檢視變得無效時(包括大小的改變,移動,被遮蓋等等),Windows 將 WM_PAINT 訊息傳送給它。該檢視的OnPaint 處理函式通過建立 CPaintDC 類的DC物件來響應該訊息並呼叫檢視的 OnDraw 成員函式。通常我們不必編寫重寫的 OnPaint 處理成員函式。
-
///CView預設的標準的重畫函式
-
void CView::OnPaint()
//見VIEWCORE.CPP
-
{
-
-
CPaintDC dc(this);
-
OnPrepareDC(&dc);
-
OnDraw(&dc);
//呼叫了OnDraw
-
}
-
///CView預設的標準的OnPrint函式
-
void CView::OnPrint(CDC* pDC, CPrintInfo*)
-
{
-
ASSERT_VALID(pDC);
-
OnDraw(pDC);
// Call Draw
-
}
既然OnPaint最後也要呼叫OnDraw,因此我們一般會在OnDraw函式中進行繪製。下面是一個典型的程式。
///檢視中的繪圖程式碼首先檢索指向文件的指標,然後通過DC進行繪圖呼叫。
-
void CMyView::OnDraw( CDC* pDC )
-
{
-
CMyDoc* pDoc = GetDocument();
-
CString s = pDoc->GetData();
-
GetClientRect( &rect );
// Returns a CString CRect rect;
-
pDC->SetTextAlign( TA_BASELINE | TA_CENTER );
-
pDC->TextOut( rect.right /
2, rect.bottom /
2, s, s.GetLength() );
-
}
最後:現在大家明白這哥倆之間的關係了吧。因此我們一般用OnPaint維護視窗的客戶區(例如我們的視窗客戶區加一個背景圖片),用OnDraw維護檢視的客戶區(例如我們通過滑鼠在檢視中畫圖)。(不明白啊???)當然你也可以不按照上面規律來,只要達到目的並且沒有問題,怎麼幹都成。
補充:我們還可以利用Invalidate(),ValidateRgn(),ValidateRect()函式強制的重畫視窗,具體的請參考MSDN吧。
OnDraw中可以繪製使用者區域。OnPaint中只是當視窗無效時重繪不會保留CClientDC繪製的內容。
這兩個函式有區別也有聯絡:
1、區別:OnDraw是一個純虛擬函式,定義為virtual void OnDraw( CDC* pDC ) = 0; 而OnPaint是一個訊息響應函式,它響應了WM_PANIT訊息,也是是視窗重繪訊息。
2、聯絡:我們一般在視類中作圖的時候,往往不直接響應WM_PANIT訊息,而是過載OnDraw純虛擬函式,這是 因為在CVIEW類中的WM_PANIT訊息響應函式中呼叫了OnDraw函式,如果在CMYVIEW類中響應了WM_PAINT訊息,不顯式地呼叫 OnDraw函式的話,是不會在視窗重繪的時候呼叫OnDraw函式的。
應用程式中幾乎所有的繪圖都在檢視的OnDraw 成員函式中發生,必須在檢視類中重寫該成員函式。(滑鼠繪圖是個特例,這在通過檢視解釋使用者輸入中討論。)
OnDraw 重寫:
通過呼叫您提供的文件成員函式獲取資料。
通過呼叫框架傳遞給 OnDraw 的裝置上下文物件的成員函式來顯示資料。
當文件的資料以某種方式更改後,必須重繪檢視以反映該更改。預設的 OnUpdate 實現使檢視的整個工作區無效。當檢視變得無效時,Windows 將 WM_PAINT 訊息傳送給它。該檢視的 OnPaint 處理函式通過建立 CPaintDC 類的裝置上下文物件來響應該訊息並呼叫檢視的 OnDraw 成員函式。
當沒有新增WM_PAINT訊息處理時,視窗重繪時,由OnDraw來進行訊息響應...(TMD,又不理解了,說錯了吧???)當新增WM_PAINT訊息處理時,視窗重繪時,WM_PAINT訊息被投遞,由OnPaint來進行訊息響應.這時就不能隱式呼叫OnDraw了.必須顯式呼叫( CDC *pDC=GetDC(); OnDraw(pDC); )..
隱式呼叫:當由OnPaint來進行訊息響應時,系統自動呼叫CView::OnDraw(&pDC).
想象一下,視窗顯示的內容和列印的內容是差不多的,所以,一般情況下,統一由OnDraw來畫。視窗前景需要重新整理時,系統會會呼叫到OnPaint,而OnPaint一般情況下是對DC作一些初始化操作後,呼叫OnDraw()。
OnEraseBkGnd(),是視窗背景需要重新整理時由系統呼叫的。明顯的一個例子是設定視窗的背景顏色(你可以把這放在OnPaint中去做,但是會使產生閃爍的現象)。
至於怎麼界定背景和前景,那要具體問題具體分析了,一般情況下,你還是很容易區別的吧。
的確,OnPaint()用來響應WM_PAINT訊息,視類的OnPaint()內部根據是列印還是螢幕繪製分別以不同的引數呼叫OnDraw()虛擬函式。所以在OnDraw()裡你可以區別對待列印和螢幕繪製。
其實,MFC在進行列印前後還做了很多工作,呼叫了很多虛擬函式,比如OnPreparePrint()等。
OnInitUpdate是VIEW的初始化
OnUpdate是文件多視時,響應其它檢視的改變
OnDraw和OnPaint都是繪圖。OnPaint呼叫OnDraw,並且呼叫OnPrepareDC
---------------------------------------------------------------
一般來說使用者的輸入/輸出基本都是通過視進行,但一些例外的情況下可能需要和框架直接發生作用,而在多視的情況下如何在視之間傳遞資料。
在使用選單時大家會發現當一個選單沒有進行對映處理時為禁止狀態,在多視的情況下選單的狀態和處理對映是和當前活動視相聯絡的,這樣MFC可以保 證視能正 確的接收到各種訊息,但有時候也會產生不便。有一個解決辦法就是在框架中對訊息進行處理,這樣也可以保證當前文件可以通過框架得到當前訊息。
在使用者進行輸入後如何使視的狀態得到更新?這個問題在一個文件對應一個檢視時是不存在的,但是現在有一個文件對應了兩個檢視,當在一個視上進行了 輸入時如何保證另一個視也得到通知呢?MFC的做法是利用文件來處理的,因為文件管理著當前和它聯絡的視,由它來通知各個視是最合適的。讓我們同時看兩個 函式:
-
void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
-
void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint =
0L, CObject* pHint =
NULL )
當文件的UpdateAllViews被呼叫時和此文件相關的所有視的OnUpdate都會被呼叫,而引數lHint和pHint都會被傳遞。這 樣一來發生改變視就可以通知其他的兄弟了。那麼還有一個問題:如何在OnUpdate中知道是那個檢視發生了改變呢,這就可以利用pHint引數,只要調 用者將this指標賦值給引數就可以了,當然完全可以利用該引數傳遞更復雜的結構。
視的初始化,當一個文件被開啟或是新建一個文件時檢視的CView::OnInitialUpdate()會被呼叫,你可以通過過載該函式對視進行初始化,並在結束前呼叫父類的OnInitialUpdate,因為這樣可以保證OnUpdate會被呼叫。
文件中內容的清除,當文件被關閉時(比如退出或是新建前上一個文件清除)void CDocument::DeleteContents ()會被呼叫,你可以通過過載該函式來進行清理工作。
在單文件結構中上面兩點尤其重要,因為軟體執行文件物件和視物件只會被產生並刪除一次。所以應該將上面兩點和C++物件構造和構析分清楚。
最後將一下文件模板(DocTemplate)的作用,文件模板分為兩類單文件模板和多文件模板,分別由CSingleDocTemplate和 CMultiDocTemplate表示,模板的作用在於記錄文件,視,框架之間的對應關係。還有一點就是模板可以記錄應用程式可以開啟的檔案的型別,當 開啟檔案時會根據文件模板中的資訊選擇正確的文件和視。模板是一個比較抽想的概念,一般來說是不需要我們直接進行操作的。
當使用者通過視修改了資料時,應該呼叫GetDocument()->SetModifiedFlag(TRUE)通知文件資料已經被更新,這樣在關閉文件時會自動詢問使用者是否儲存資料。
OnDraw()和OnPaint()好象兄弟倆,因為它們的工作類似。
至於不見了的問題簡單,因為當你的視窗改變後,會產生無效區域,這個無效的區域需要重畫。一般Windows回傳送兩個訊息WM_PAINT(通 知客戶區有變化)和WM_NCPAINT(通知非客戶區有變化)。非客戶區的重畫系統自己搞定了,而客戶區的重畫需要我們自己來完成。這就需要 OnDraw()或OnPaint()來重畫視窗。
-
void CBaseClasse::OnPaint()
-
{
-
CDialog::OnPaint();
-
}
-
BOOL CBaseClasse::OnEraseBkgnd(CDC* pDC)
-
{
-
// TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值
-
CRect rcClient;
-
GetClientRect(&rcClient);
-
CDC dc;
-
LoadPicture(dc);
-
return TRUE;
-
}
-
void CBaseClasse::LoadPicture(CDC& dcMemory)
-
{
-
CString strFileName = m_strFileName;
-
int nPicMode = m_nPicMode;
-
COLORREF crBg = m_crBg;
-
CRect rc;
-