天天看點

MFC中改變對話框背景的幾個消息函數OnEraseBkgnd、 OnPaint、 OnCtlColor的調用順序

設定對話框背景顔色及背景圖檔可在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就可以了,如下示例

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

{

if(pWnd->GetDlgCtrlID()==IDC_STATIC5)

   {

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

    pDC->SelectObject(&m_font);

    m_font.Detach();            

    pDC->SetBkMode(TRANSPARENT);  

    return (HBRUSH)::GetStockObject(NULL_BRUSH);      

   }

}

但是如果在OnCtlColor()在設定背景圖檔,則圖檔不會随對話框大小按比例縮放

是以可調用StretchBlt()函數設定,如下示例:

void CDb3Dlg::OnPaint()

CClientDC cdc(this); CDC comdc;

comdc.CreateCompatibleDC(&cdc);

CBitmap bitmap;

bitmap.LoadBitmap(IDB_BITMAP2);

comdc.SelectObject(&bitmap);

CRect rect;

GetClientRect(rect);

BITMAP bit;

bitmap.GetBitmap(&bit);

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()這條語句。

繼續閱讀