天天看點

MFC學習--對話框及視窗背景色 .

設定對話框背景顔色及背景圖檔可在OnCtlColor(),OnEraseBkgnd(),OnPaint()裡設定,對話框初始化完畢,顯示視窗時按順序調用OnSize()>OnEraseBkgnd()>OnPaint()>OnCtlColor()。

OnEraseBkgnd()中預設調用基類(CFrameWnd)的OnEraseBkgnd(), 用視窗類中注冊的Brush繪制背景。是以,繪制視窗背景時最好重寫OnEraseBkgnd(),但後面不能調用基類的OnEraseBkgnd()。

同時,MSDN關 WM_ERASEBKGND消息的說明中有寫道,DefWindowProc使用視窗類注冊的背景Brush繪制背景,如果背景Brush是NULL,則需處理處理WM_ERASEBKGND消息。言外之意,處理WM_ERASEBKGND消息但不傳給DefWindowProc就可以自己繪制背景,系統又不會重複繪制一遍。

When this member(背景畫刷) is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.

方法如下:

BOOL CMainWindow::OnEraseBkgnd(CDC* pDC)  

{  

    // TODO: 在此添加消息處理程式代碼和/或調用預設值   

    CRect rc;  

    GetClientRect(&rc);  

    pDC->FillSolidRect(&rc ,  RGB(0,0,255)  );   

    //return CFrameWnd::OnEraseBkgnd(pDC);   

    return TRUE;  

}  

 當然,也可以重寫OnEraseBkgnd()直接傳回TRUE,然後在OnPaint()中改變背景。

更直接的方法是修改視窗類,實作更改背景顔色

BOOL CMainWindow::PreCreateWindow(CREATESTRUCT& cs)  

    // TODO: 在此添加專用代碼和/或調用基類   

    if( CFrameWnd::PreCreateWindow(cs))  

    {     

        //改變視窗類   

        WNDCLASS   wndclass;   

        ::GetClassInfo(AfxGetInstanceHandle(),cs.lpszClass,&wndclass);   

        //wndclass.hbrBackground   =   (HBRUSH)(COLOR_3DFACE   +   1);    

        //wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);   

        wndclass.hbrBackground=CreateSolidBrush(RGB(0,100,100));  

        wndclass.hbrBackground=m_BKBrush;//m_BKBrush不能為函數局部變量   

        wndclass.hbrBackground=*(new CBrush(RGB(25,25,0)));//最友善的方法   

        //wndclass.hCursor   =   AfxGetApp()-> LoadCursor(IDC_CURSOR1);    

        wndclass.lpszClassName   =   _T("newViewClassName ");   

        VERIFY(AfxRegisterClass(&wndclass));   

        cs.lpszClass=wndclass.lpszClassName;  

        return TRUE;  

    }  

    return FALSE;  

若改變對話框大小,比如全屏顯示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格式)  

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

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

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

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

繼續閱讀