關於WM_ERASEBKGND和WM_PAINT的深刻理解


一直以來,對於WM_PAINT和WM_ERASEBKGND消息不是很清楚,從書上和網上找了很多資料,大體上有以下幾點說法:
1>WM_PAINT先產生,WM_ERASEBKGND后產生

2.WM_PAINT產生后,在調用BeginPaint時
hdc = BeginPaint(hWnd, &ps); 
如果ps.fErase為true,則BeginPaint會產生WM_ERASEBKGND消息

3.BeginPaint函數用來擦除窗口背景

4.WM_ERASEBKGND用來繪制背景

經過調試、分析,發現上面的說法並不正確。以下是一些測試代碼,代碼后面附上一些分析。最后總結出幾點,可以解釋程序中出現的所有關於窗口重繪的問題。
如有不正確的地方,大家可以指正。

為了說明問題,在此不說WM_NCPAINT消息(非客戶區消息),只說WM_ERASEBKGND消息和客戶區的WM_PAINT消息

//此段代碼摘自vc6應用程序向導自動生成的代碼,並添加了一些測試代碼
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 int wmId, wmEvent;
 PAINTSTRUCT ps;
 HDC hdc;
 TCHAR szHello[MAX_LOADSTRING];
 LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
 
 switch (message) 
 {
 case WM_COMMAND:
  wmId    = LOWORD(wParam); 
  wmEvent = HIWORD(wParam); 
  // Parse the menu selections:
  switch (wmId)
  {
  case IDM_ABOUT:
   DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
   break;
  case IDM_EXIT:
   DestroyWindow(hWnd);
   break;
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
  }
  break;
  case WM_ERASEBKGND: //如果處理了這個消息,則默認消息處理函數不會調用,背景就不會繪制
   {
    static int iCount=0;
    char ch[MAX_PATH];
    sprintf(ch,"%d ---------WM_ERASEBKGND\n",iCount); //這個函數需要包含#include<stdio.h>
    OutputDebugString(ch); //調試時便於觀察
    iCount++;
   break;
   }
  case WM_PAINT:
   {
    OutputDebugString("  -------------WM_PAINT\n");
    hdc = BeginPaint(hWnd, &ps); //使無效區域變得有效,並填充ps結構
    // TODO: Add any drawing code here...
    
    //繪制一個藍色橢圓,ps.rcPaint保存了客戶區矩形
    HBRUSH hbrush=::CreateSolidBrush(RGB(0,0,255)); 
    ::SelectObject(hdc,hbrush);
    ::Ellipse(hdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
    ::DeleteObject(hbrush);
    
    EndPaint(hWnd, &ps);
    break;
   }
  case WM_LBUTTONDOWN: //調用DefWindowProc擦除客戶區背景
   {
    HDC hdc;
    hdc=::GetDC(hWnd);
    WPARAM w=(WPARAM)hdc;
    LPARAM l=0;
    DefWindowProc(hWnd, WM_ERASEBKGND, w, l);
   }
   break;
  case WM_DESTROY:
   PostQuitMessage(0);
   break;
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;
}

 

先說一下程序運行時發現的一些現象:
1.
上面的代碼:如果添加了WM_ERASEBKGND消息,里面什么也不做,如下
case WM_ERASEBKGND:
  
  break;

則當程序運行時,如果收到WM_ERASEBKGND消息,則這個switch-case結構中就不會執行默認消息處理函數DefWindowProc,運行時發現,窗口的背景就沒有了,即背景為空。
這說明了窗口背景僅僅是由默認的消息處理函數DefWindowProc繪制的。
(注:注冊窗口類時,背景設置為白色wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);)


2.
如上面的代碼,因為有WM_ERASEBKGND消息,則程序運行時窗口背景為NULL,
但是如果添加了WM_LBUTTONDOWN消息,從里面調用默認窗口消息處理函數,如下
case WM_LBUTTONDOWN:
  {
   HDC hdc;
   hdc=::GetDC(hWnd);
   WPARAM w=(WPARAM)hdc; //變量w作為WM_ERASEBKGND消息的wParam參數,保存了設備環境句柄
   LPARAM l=0;
   DefWindowProc(hWnd, WM_ERASEBKGND, w, l); //調用默認消息處理函數DefWindowProc
  }
  break;
程序運行時,如果用鼠標單擊一下窗口客戶區,則窗口的背景就會顯示!這進一步說明了窗口的背景色是由默認消息處理函數DefWindowProc繪制的。

3.
以上代碼,因為添加了WM_ERASEBKGND消息,所以窗口背景是空。
雖然在WM_PAINT消息中有
hdc = BeginPaint(hWnd, &ps);函數的調用,但是窗口背景仍然是空,這說明了BeginPaint函數並不會擦除背景(即用默認畫刷繪制窗口背景)。

BeginPaint函數只做了兩件事情:
1》使窗口無效區域變得有效,從而使Windows不再發送WM_PAINT消息(直到窗口大小改變等,使窗口再次變得無效)。
(如果窗口一直無效,則Windows會不停地發送WM_PAINT消息)

1》填充PAINTSTRUCT結構。填充這個結構的目的,是讓程序員可以根據ps變量中的標志值進行某些操作

4.
調試的時候,發現:當窗口改變大小,或者其它操作使窗口變得無效時,WM_ERASEBKGND消息總是先於WM_PAINT消息發出,而且如果產生WM_ERASEBKGND消息,
則最后一個WM_ERASEBKGND的下一條消息一定是WM_PAINT消息(WM_ERASEBKGND可能會連續產生幾次)。WM_ERASEBKGND消息和WM_PAINT消息之間沒有其它消息


--------------------------------------------
以下是一些總結

1.窗口背景的擦除(即繪制)

窗口的背景色是由默認的消息處理函數DefWindowProc擦除的(即這個函數使用注冊窗口類時使用的背景刷擦除窗口背景)。
什么時候繪制?在窗口函數收到WM_ERASEBKGND消息,DefWindowProc函數以WM_ERASEBKGND為參數,才會繪制窗口背景
(注:當WM_ERASEBKGND消息產生后,窗口一定有一部分變得無效)

2.窗口的無效:

當拖動窗口的一個頂點改變了窗口的大小、窗口由最小化恢復到最大化、窗口的一部分被其它窗口遮住又重新顯示、調用MoveWindow函數改變了窗口大小、窗口移動到桌面之外的

部分被拖回重新顯示時,窗口就會變得無效。 無效區域是整個客戶區,因此默認窗口處理函數DefWindowProc會擦除整個客戶區。
(注:拖動窗口標題欄移動窗口,只要窗口沒有移動到屏幕之外,那么這兩個消息都不產生)

當窗口無效時,Windows會給窗口發出WM_ERASEBKGND消息和WM_PAINT消息,而且WM_ERASEBKGND先發出一次或者幾次,緊接着是WM_PAINT


例外:InvalidateRect函數的調用會使窗口變得無效,並產生WM_ERASEBKGND消息和WM_PAINT消息,而WM_ERASEBKGND是否產生取決於參數bErase

void InvalidateRect ( 
LPCRECT lpRect, 
BOOL bErase = TRUE ); 
當參數bErase為true時,WM_ERASEBKGND消息產生,當bErase為false時WM_ERASEBKGND消息不產生


3.消息的處理過程
當窗口無效時,

先發出WM_ERASEBKGND消息若干次-----------再發出WM_PAINT消息,WM_ERASEBKGND和WM_PAINT之間沒有其它消息
WM_ERASEBKGND消息的后面一定是WM_PAINT

1》WM_ERASEBKGND消息的處理:
上面的代碼,如果沒有添加WM_ERASEBKGND,則默認的消息處理函數DefWindowProc會被調用,此時的DefWindowProc會擦除窗口背景(即繪制背景),並且ps.fErase會為FALSE

如果添加了WM_ERASEBKGND消息,DefWindowProc就不會被調用,則無法擦除窗口背景,並且ps.fErase會為true

2》WM_PAINT的處理
在這個消息中如果調用了hdc = BeginPaint(hWnd, &ps);函數,則此函數只做了兩件事:填充ps結構、使窗口重新變得有效

另外DefWindowProc函數也會使窗口變得有效

關於ps.fErase;
這個參數和窗口函數WndProc的返回值有關:
當窗口函數WndProc返回true;則產生WM_PAINT消息時,ps.fErase就為false;表明系統擦除了背景
當窗口函數WndProc返回false;則產生WM_PAINT消息時,ps.fErase就為true;表明系統沒有擦除背景

設想一下,當上面的代碼中添加了WM_ERASEBKGND消息並在其中直接返回true(這表明系統已經繪制了窗口背景),則ps.fErase就為false
case WM_ERASEBKGND: 
return true; //窗口函數WndProc返回true;

注意返回的真或者假只是讓程序員可以看見ps.fErase,並作出自己的代碼,與窗口的顯示即背景沒有關系
有些人說當ps.fErase==true,BeginPaint函數會發送一個WM_ERASEBKGND消息,其實BeginPaint並未發出WM_ERASEBKGND消息

4.自己繪制背景或者系統繪制背景。
如果程序員不想系統擦除背景,而自己想繪制背景,怎么辦呢?方法是在WM_ERASEBKGND消息處理中添加自己的繪制代碼。
對於WM_ERASEBKGND消息,wParam參數保存了用於繪制的設備環境,lParam不使用。

如上面的示例代碼,當添加了WM_ERASEBKGND消息,則switch---case中就不會調用DefWindowProc函數繪制背景。這時,程序員自己就可以添加繪制代碼
而在基於MFC的程序中,是這樣處理自繪代碼的:

BOOL CCeDlg::OnEraseBkgnd(CDC* pDC) //這個函數就是WM_ERASEBKGND的消息處理函數
{
 // TODO: Add your message handler code here and/or call default
//添加自繪代碼
...
return TRUE; //返回真,代表着窗口函數的返回值。以便於程序員在WM_PAINT消息中作出相應處理(如果需要)。這里返回時就不會調用下面的默認處理

//下面將調用系統默認的消息處理函數DefWindowProc進行背景的默認繪制。
 return CDialog::OnEraseBkgnd(pDC); //不執行自動生成的這個函數
}

執行這個函數時,提示用戶繪制背景,如果用戶沒有繪制背景,則return CDialog::OnEraseBkgnd(pDC);調用默認的窗口處理函數進行背景的擦除


BOOL CCeDlg::OnEraseBkgnd(CDC* pDC) 
{
 // TODO: Add your message handler code here and/or call default
//添加自繪背景代碼
 CBitmap m_bitmap;
 BITMAP m_bmInfo;
 m_bitmap.LoadBitmap(IDB_BITMAP1);
 m_bitmap.GetObject(sizeof(m_bmInfo),&m_bmInfo);

 CDC memDC;
 memDC.CreateCompatibleDC(pDC);
 memDC.SelectObject(&m_bitmap);

 GetClientRect(m_rect);
 pDC->StretchBlt(0,0,m_rect.Width(),m_rect.Height(),

&memDC,0,0,m_bmInfo.bmWidth,m_bmInfo.bmHeight,SRCCOPY); //內存拷貝函數。繪制背景
 memDC.DeleteDC();

 return true;//返回真,代表着窗口函數的返回值。以便於程序員在WM_PAINT消息中作出相應處理(如果需要)。這里返回時就不會調用下面的默認處理

//下面將調用系統默認的消息處理函數DefWindowProc進行背景的默認繪制。
 return CDialog::OnEraseBkgnd(pDC); //不執行自動生成的這個函數
}

5.WM_ERASEBKGND消息和WM_PAINT消息的另外一種含義:背景色與前景色

可以這樣理解WM_ERASEBKGND消息和WM_PAINT消息:

WM_ERASEBKGND消息用於通知系統或者程序員繪制背景色
WM_PAINT消息用於通知程序員繪制前景色,比如在WM_PAINT中調用TextOut函數輸出文本

http://blog.csdn.net/sdeeds/article/details/6859530


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM