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


免責聲明!

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



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