視圖的背景一般來說是白色的,在缺省情況下,它和系統定義的顏色COLOR_WINDOW是一致的。設計者一般會希望自己的程序可以讓用戶輕松地改變窗口背景顏色,或是用漂亮的圖片來充填背景。我們可以用Windows函數SetSysColors來重新指定COLOR_WINDOW所對應的實際顏色,來達到改變視圖背景顏色的目的。但這樣會同時改變其他應用程序的視圖窗口背景,使得整個Windows系統的顏色設置產生混亂。另外,我們可能會用以下方法來設置視圖的背景顏色,即在CView的OnDraw函數中添寫如下一段程序代碼:
void CTestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect rectClient; CBrush brushBkColor; GetClientRect(rectClient); brushBkColor.CreateSolidBrush(RGB(255,0,0)); pDC->DPtoLP(rectClient); pDC->FillRect(rectClient,&brushBkColor); … }
這樣可以達到改變當前應用程序的視圖背景的目的,但同時也產生了一些不良影響,使得程序運行效果不盡如人意。
分析問題
我們知道,在VC++的文檔、視結構中,CView的OnDraw函數用於實現絕大部分圖形繪制的工作。如果用戶改變窗口尺寸,或者顯示隱藏的區域,OnDraw函數都將被調用來重畫窗口。並且,當程序文檔中的數據發生改變時,一般必須通過調用視圖的Invalidate(或InvalidateRect)成員函數來通知Windows所發生的改變,對Invalidate的調用也會觸發對OnDraw函數的調用。正因為OnDraw函數被頻繁調用,所以在其執行時,每次都刷新填充一次視圖客戶區域,便會使屏幕不穩定,產生閃爍現象。
對VC++應用程序框架結構和Windows消息映射系統的仔細研究,找到另外一種改變視圖背景的方法,其執行效果比上述兩種方法都好。其實在程序調用OnDraw函數之前,會觸發一個Windows消息:WM_ERASEBKGND,以擦除視圖刷新區域。在缺省情況下,Windows系統使用視圖窗口注冊時窗口類中的成員hbrBackground所描述的畫刷來擦除屏幕,這一般會將屏幕刷新成COLOR_WINDOW所對應的顏色。因此,在OnDraw函數中設置背景顏色的執行過程是這樣的:先將屏幕刷新成COLOR_WINDOW所對應的顏色(白色),接着又在OnDraw函數中填充其他顏色(綠色刷背景,或者視圖用別顏色畫圖),這正是產生屏幕閃爍的根本原因。
對WM_PAINT的處理幾乎總是從一個BeginPaint調用開始:hdc = BeginPaint (hwnd, &ps) ;而以一個EndPaint調用結束,中間條用OnPaint()函數:在BeginPaint調用中,如果顯示區域的背景還未被刪除,則由Windows來刪除。(即用默認白色畫刷在刷一遍屏幕,即重繪背景色)它使用注冊窗口類別的WNDCLASS結構的hbrBackground字段中指定的畫刷來刪除背景。一般, 這是一個白色備用畫刷。這意味着,Windows將通過把窗口背景設定為白色來刪除窗口背景。
解決問題
通過上述分析,我們應將視圖背景顏色填充移到Windows消息:WM_ERASEBKGND所對應的消息映射函數中,而不是在OnDraw函數中。我們可以通過下列步驟實現這一過程:在文檔類中增加一成員變量m_viewBkColor保存當前背景顏色,同時增加兩個成員函數GetViewBkColor和SetViewBkColor對其進行讀寫操作。這樣做的好處是可以對m_viewBkColor成員進行序列化,將其和文檔聯系在一起,打開某一文檔時,其背景將和上一次程序操作該文檔時的背景保持一致。在視圖類中為視圖的Windows消息WM_ERASEBKGND增加消息映射函數OnEraseBkgnd,代碼如下:
BOOL CTestView::OnEraseBkgnd(CDC* pDC) { CRect rect; CBrush brush; brush.CreateSolidBrush(GetDocument()->GetViewBkColor()); pDC->GetClipBox(rect); pDC->FillRect(rect,&brush); return true; }