1. 程式人生 > >MFC - 工作中學習

MFC - 工作中學習

1,

CPaintDC:
(1)用於響應視窗重繪訊息(WM_PAINT)是的繪圖輸出。
(2)CPaintDC在建構函式中呼叫BeginPaint()取得裝置上下文,在解構函式中呼叫EndPaint()釋放裝置上下文。EndPaint()除了釋放裝置上下文外,還負責從訊息佇列中清除WM_PAINT訊息。因此,在處理視窗重畫時,必須使用CPaintDC,否則WM_PAINT訊息無法從訊息佇列中清除,將引起不斷的視窗重畫。
(3)CPaintDC也只能用在WM_PAINT訊息處理之中。

eg:

void XXDlg::OnPaint()
{
    CPaintDC dc(this);
}

2,CDC

https://blog.csdn.net/maryzhao1985/article/details/6653675

3,OnCtlColor(),OnEraseBkgnd(),OnPaint() --- 轉載

設定對話方塊背景顏色及背景圖片可在OnCtlColor(),OnEraseBkgnd(),OnPaint()裡設定,對話方塊初始化完畢,顯示時呼叫OnSize()->OnEraseBkgnd(),->OnPaint()->OnCtlColor(),

若想改變對話方塊大小,比如全屏顯示ShowWindow(SW_SHOWMAXIMIZED);UpdateWindow();

其中 ShowWindow會呼叫OnSize()->OnEraseBkgnd(),

        UpdateWindow();呼叫OnPaint()->OnCtlColor(),

      若對話方塊中沒有設定訊息響應OnEraseBkgnd(),,則系統預設訊息響應OnEraseBkgnd()會呼叫OnCtlColor()設定對話方塊背景(即替代OnEraseBkgnd())

      對話方塊的背景設定可在OnCtlColor()中進行,因為OnCtlColor()一般會被多次呼叫,所以要想設定的CFont,CBrush等應在OnInitDialog中初始化,若要在OnCtlColor()中設定,在設定前先呼叫Detach就可以了,如下示例

 

 
  1. HBRUSH CDb3Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

  2. {

  3.  
  4. if(pWnd->GetDlgCtrlID()==IDC_STATIC5)

  5. {

  6.  
  7. m_font.CreatePointFont(300,"宋體");

  8. pDC->SelectObject(&m_font);

  9. m_font.Detach();

  10. pDC->SetBkMode(TRANSPARENT);

  11. return (HBRUSH)::GetStockObject(NULL_BRUSH);

  12. }

  13.  
  14. }


 

但是如果在OnCtlColor()在設定背景圖片,則圖片不會隨對話方塊大小按比例縮放

所以可呼叫StretchBlt()函式設定,如下示例:

 

 
  1. void CDb3Dlg::OnPaint()

  2. {

  3.  
  4. CClientDC cdc(this); CDC comdc;

  5. comdc.CreateCompatibleDC(&cdc);

  6. CBitmap bitmap;

  7. bitmap.LoadBitmap(IDB_BITMAP2);

  8. comdc.SelectObject(&bitmap);

  9. CRect rect;

  10. GetClientRect(rect);

  11. BITMAP bit;

  12. bitmap.GetBitmap(&bit);

  13. cdc.StretchBlt(0,0,rect.Width(),rect.Height(),&comdc,0,0,bit.bmWidth,bit.bmHeight,SRCCOPY);


 

}//全屏顯示對話方塊背景圖片(限bmp格式)

    用了兩年的VC,其實對OnPaint的工作原理一直都是一知半解。這兩天心血來潮,到BBS上到處發帖詢問,總算搞清楚了,現在總結一下。

     對於視窗程式,一般有個特點:視窗大部分的區域保持不變,只有不分割槽域需要重新繪製。如果將整個視窗全部重新整理的畫,就做了許多不必要的工作,因而,MFC採用了一套基於無效區的處理機制。在分析無效區處理之前,我們要明白一個現實,現在的機器還不夠牛,如果夠牛的話,我們乾脆將整個視窗不斷的重新繪製好了。事實上即使夠牛也不行,對於一個單執行緒程式,通過一個while迴圈不斷的重新整理視窗,程式也無法相應其他訊息(除非使用多執行緒),看來使用無效區的處理機制還是有其必然性的。

     VC程式是基於訊息機制的,你所做的任何操作,比如點選滑鼠,拖動視窗,首先進入系統的訊息佇列。這裡的系統訊息佇列包括多個程式的訊息,系統再將訊息傳送給相應的程式。既然是佇列,這就有一個先進先出的問題,螢幕上的無效區更新訊息出現的頻率就會特別高。比如當左上角更新的訊息還沒有處理,右下角更新的訊息已經過來了。為了避免多次處理WM_PAINT訊息,系統就將這些視窗更新訊息合併到一條,只是將無效區範圍變成包括這兩次更新無效區範圍在內的矩形區域。這樣就減少了WM_PAINT訊息的處理次數,提高了效率。

     那麼,在OnPaint訊息處理函式中,又是怎樣實現更新無效區的呢?首先,要明白MFC中所有繪圖操作都是基於裝置描述表(Device Context,簡稱DC)的,具體資訊可參看任何一本VC教材。DC中包含了繪圖裝置的各種資訊,對於螢幕繪圖,其實就是有一塊記憶體(視訊記憶體),專門用來存放要顯示到螢幕上的資訊,顯示器以85HZ的頻率(我以前的顯示器)將其內容重新整理的螢幕上。這裡就到了關鍵點,顯示器的重新整理是將視訊記憶體中的內容完全更新到顯示器上,不存在無效區處理的問題,那麼,無效區的處理一定發生在DC的繪圖處理上。事實確實如此,當程式呼叫OnPaint訊息時,首先將無效區範圍傳遞給DC,DC在進行繪圖操作時,就只更新無效區範圍內的資訊,其他地方的不管,這就提高了效率。開啟OnPaint函式有下面三種選擇:

1)  直接傳送WM_PAINT訊息,用PostMessage(),SendMessage()函式傳送WM_PAINT訊息。使用以上兩函式傳送WM_PAINT訊息,能將WM_PAINT訊息傳送到WINDOWS程式訊息佇列中,當WINDOWS將WM_PAINT訊息傳送給具體的訊息處理函式時,如果視窗的無效區域為空則WINDOWS將不理睬該訊息。若存在無效區域,則呼叫視窗處理函式處理。要注意的這裡需要存在無效區域,因此要呼叫2)中的函式使得窗體(或者部分)無效,其處理過程與2)相同,將WM_PAINT訊息送入訊息處理佇列。與3)不同的是WM_PAINT並不立即處理;

2)  呼叫相應的API實現WM_PAINT訊息的傳送:Invalidate(),InvalidateRect(), InvalidateRgn():以上函式將視窗的特定區域標定為無效,當WINDOWS檢測到視窗中存在無效區域時將向訊息佇列傳送WM_PAINT 訊息。我當時用的就是Invalidate()函式;

3)  UpdateWindow():該函式呼叫後WINDOWS將向視窗傳送一個非佇列化的WM_PAINT訊息,它不經過訊息迴圈而直接傳送給了視窗訊息處理函式。如果視窗無效區域不存在,WINDOWS將不理睬該訊息。注意這裡因為要使得視窗無效區不存在,因此還是呼叫Invalidate(),InvalidateRect(), InvalidateRgn()函式,和2)中不同的是這裡的WM_PAINT訊息會被立即處理,而2)中是加入訊息處理佇列。

簡單起見,你可以使用2)中方案進行問題解決。

     現在你明白OnPaint的處理是怎麼一回事了吧?這裡還想說一下Invalidate和UpdateWindow的區別。Invalidate在訊息佇列中加入一條WM_PAINT訊息,其無效區為整個客戶區。而UpdateWindow直接傳送一個WM_PAINT訊息,其無效區範圍就是訊息佇列中WM_PAINT訊息(最多隻有一條)的無效區。效果很明顯,呼叫Invalidate之後,螢幕不一定馬上更新,因為WM_PAINT訊息不一定在佇列頭部,而呼叫UpdateWindow會使WM_PAINT訊息馬上執行的,繞過了訊息佇列。如果你呼叫Invalidate之後想馬上更新螢幕,那就加上UpdateWindow()這條語句。