重繪導致原因:UpdateData、Invalidate、InvalidateRect和UpdateWindow函數。
1. UpdateData重繪控件函數
UpdateData(TRUE)——刷新控件的值到對應的變量。(外部輸入值交給內部變量)
UpdateData(FALSE) —— 拷貝變量值到控件顯示。(變量的最終運算結果值交給外部輸出顯示)
即:變量值—>控件顯示。
2. Invalidate()
該函數的作用是使整個窗口客戶區無效。窗口的客戶區無效意味着需要重繪,例如,如果一個被其它窗口遮住的窗口變成了前台窗口,那么原來被遮住的部分就是無效的,需要重繪。這時Windows會在應用程序的消息隊列中放置WM_PAINT消息。MFC為窗口類提供了WM_PAINT的消息處理函數OnPaint,OnPaint負責重繪窗口。視圖類有一些例外,在視圖類的OnPaint函數中調用了OnDraw函數,實際的重繪工作由OnDraw來完成。參數bErase為TRUE時,重繪區域內的背景將被擦除,否則,背景將保持不變。
3. InvalidateRect
用InvalidateRect函數只重繪部分區域,而且不重繪背景(第二個參數用FALSE)就可以解決大部分的屏閃問題。
比如:CRect rect(10,47,10+120,47+70);
InvalidateRect(rect,FALSE);
4. UpdateWindow函數
UpdateWindow( )的作用是使窗口立即重繪。調用Invalidate等函數后窗口不會立即重繪,這是由於WM_PAINT消息的優先級很低,它需要等消息隊列中的其它消息發送完后才能被處理。調用UpdateWindow函數可使WM_PAINT被直接發送到目標窗口,從而導致窗口立即重繪。注意:函數繞過應用程序的消息隊列,直接發送WM_PAINT消息給指定窗口的窗口過程,如果更新區域為空,則不發送消息。
解決方法:
雙緩沖是一種基本的技術。我們知道,如果窗體在響應WM_PAINT消息的時候要進行復雜的圖形處理,那么窗體在重繪時由於過頻的刷新而引起閃爍現象。解決這一問題的有效方法就是雙緩沖技術。
因為窗體在刷新時,總要有一個擦除原來圖象的過程,它利用背景色填充窗體繪圖區,然后在調用新的繪圖代碼進行重繪,這樣一擦一寫造成了圖象顏色的反差。當WM_PAINT的響應很頻繁的時候,這種反差也就越發明顯。於是我們就看到了閃爍現象。(
當窗口由於任何原因需要重繪時,
總是先用背景色將顯示區清除,然后才調用OnPaint,而背景色往往與繪圖內容
反差很大,這樣在短時間內背景色與顯示圖形的交替出現,使得顯示窗口看起來
在閃。如果將背景刷設置成NULL,這樣無論怎樣重繪圖形都不會閃了。
當然,這樣做會使得窗口的顯示亂成一團,因為重繪時沒有背景色對原來
繪制的圖形進行清除,而又疊加上了新的圖形。) 我們會很自然的想到,避免背景色的填充是最直接的辦法。但是那樣的話,窗體上會變的一團糟。因為每次繪制圖象的時候都沒有將原來的圖象清除,造成了圖象的殘留,於是窗體重繪時,畫面往往會變的亂七八糟。所以單純的禁止背景重繪是不夠的。我們還要進行重新繪圖,但要求速度很快,於是我們想到了使用BitBlt函數。它可以支持圖形塊的復制,速度很快。我們可以先在內存中作圖,然后用此函數將做好的圖復制到前台,同時禁止背景刷新,這樣就消除了閃爍。以上也就是雙緩沖繪圖的基本的思路。
BitBlt函數執行顏色數據的位塊傳送,從指定的源設備描述表向給定的一個目的設備描述表傳送對應於一個象素矩形的顏色數據。
BOOL BitBlt(HDC hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
HDC hdcSrc,
int nXSrc,
int nYSrc,
DWORD dwRop);
具體實現:在窗口類中定義成員變量與成員函數:
CBrush m_brush; CDC m_memDC;//畫在內存上 CBitmap m_Bmp; CWnd *m_pDrawWnd; void InitialDBB(); void DrawOnMem(double *pdData, unsigned long DataLen); void DrawOnStaticArea(double *pdData, unsigned long DataLen); 在該類的初始化函數中添加 BOOL CdaexpDlg::OnInitDialog() { 。。。。。。 m_brush.CreateSolidBrush(RGB(25,200,25)); m_pDrawWnd=GetDlgItem(IDC_PICTURE); InitialDBB(); 。。。。。。 } 在重繪函數中添加 void CdaexpDlg::OnPaint() { 。。。 //已經有圖像保存在緩沖區了 PAINTSTRUCT ps; CRect rt; m_pDrawWnd->GetClientRect(&rt); CDC* pDC=m_pDrawWnd->BeginPaint(&ps); pDC->BitBlt(0, 0, rt.Width(), rt.Height(), &m_memDC, 0, 0, SRCCOPY); m_pDrawWnd->EndPaint(&ps); 。。。 } /****************************************************************************************************** 初始化內存和畫布 *******************************************************************************************************/ void CdaexpDlg::InitialDBB() { CRect rt; m_pDrawWnd->GetClientRect(&rt); CDC* sDC = m_pDrawWnd->GetDC(); // 為屏幕DC創建兼容的內存DC if(!m_memDC.CreateCompatibleDC(sDC))// { ::PostQuitMessage(0); } m_nHeight = rt.bottom - rt.top; m_nWidth = rt.right - rt.left; // 創建位圖,不能是m_memDC,否則無顏色 m_Bmp.CreateCompatibleBitmap(sDC, rt.Width(), rt.Height());//m_memDC // 相當於選擇畫布,m_pDrawWnd-> ::SelectObject(m_memDC.GetSafeHdc(), m_Bmp); m_pDrawWnd->ReleaseDC(sDC); } /****************************************************************************************************** 繪制輸出波形至內存 *******************************************************************************************************/ void CdaexpDlg::DrawOnMem( ) { unsigned long i; //--------------------------------------------------- //獲取繪圖區域 CRect rect; m_pDrawWnd->GetClientRect(&rect); //---------------------------------------------------- //將背景塗黑 COLORREF crl = RGB(0,0,0); m_memDC.FillSolidRect(rect, crl); //---------------------------------------------------- //設置畫筆為黑色 CPen pen(PS_SOLID,1,RGB(25,200,25)); m_memDC.SelectObject(&pen); // ====================================================== // = 畫出波形 m_memDC.MoveTo( , ); m_memDC.LineTo( , ); } /****************************************************************************************************** 將內存內的波形繪制到界面 *******************************************************************************************************/ void CdaexpDlg::DrawOnStaticArea( ) { CWnd* pWnd = GetDlgItem(IDC_PICTURE);//獲得靜態文本框的窗口對象 CRect rect; pWnd->GetClientRect(&rect); CDC* pDC = pWnd->GetDC(); DrawOnMem( ); // 一次性的將內存設備環境上繪制完畢的圖形"貼"到屏幕上 pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &m_memDC, 0, 0, SRCCOPY); pWnd->ReleaseDC(pDC); }
如何提高繪圖的效率
電力系統的網絡圖形的CAD軟件,在一個窗口中往往要顯示成千上萬個電力元件,而每個元件又是由點、線、圓等基本圖形構成。如果真要在一次重繪過程重畫這么多元件,可想而知這個過程是非常漫長的。如果加上了圖形的瀏覽功能,鼠標拖動圖形滾動時需要進行大量的重繪,速度會慢得讓用戶將無法忍受。怎么辦?只有再研究研究MFC的繪圖過程了。
實際上,在OnDraw(CDC *pDC)中繪制的圖並不是所有都顯示了的,例如:你
在OnDraw中畫了兩個矩形,在一次重繪中雖然兩個矩形的繪制函數都有執行,但是很有可能只有一個顯示了,這是因為MFC本身為了提高重繪的效率設置了裁剪區。裁剪區的作用就是:只有在這個區內的繪圖過程才會真正有效,在區外的是無效的,即使在區外執行了繪圖函數也是不會顯示的。因為多數情況下窗口重繪的產生大多是因為窗口部分被遮擋或者窗口有滾動發生,改變的區域並不是整個圖形而只有一小部分,這一部分需要改變的就是pDC中的裁剪區了。因為顯示(往內存或者顯存都叫顯示)比繪圖過程的計算要費時得多,有了裁剪區后顯示的就只是應該顯示的部分,大大提高了顯示效率。但是這個裁剪區是MFC設置的,它已經為我們提高了顯示效率,在進行復雜圖形的繪制時如何進一步提高效率呢?那就只有去掉在裁剪區外的繪圖過程了。可以先用pDC->GetClipBox()得到裁剪區,然后在繪圖時判斷你的圖形是否在這個區內,如果在就畫,不在就不畫。
如果你的繪圖過程不復雜,這樣做可能對你的繪圖效率不會有提高。