雙緩沖解決VC++繪圖時屏幕閃爍
通常來說程序根據(jù)需要調(diào)用Invalidate(FALSE)使窗口客戶區(qū)無效引起重繪,然后在窗口OnPaint函數(shù)(基于文檔視圖的程序則是OnDraw)中進行穩(wěn)定繪圖就行了。但是,我們在OnPaint中進行多重繪制(畫背景、棋盤、棋子等),前后繪制的反差造成了閃爍現(xiàn)象。以前知道Java中解決屏幕閃爍問題是用雙緩沖的方法,現(xiàn)在發(fā)現(xiàn)在vc++中也是可以這么做的。簡單來說,雙緩沖就是先把需要繪制的東西全部一口氣畫在內(nèi)存中,最后把內(nèi)存中的數(shù)據(jù)搬到屏幕上顯示。
最近做中國象棋,繪制界面時遇到些問題,繪圖過程中屏幕閃爍,估計都會想到利用雙緩沖來解決問題,但查了下網(wǎng)上雙緩沖的資料,發(fā)現(xiàn)基本是MFC的,轉(zhuǎn)化為VC++后,大概代碼如下:
void DrawBmp(HDC hDC, HBITMAP hBitmap) { HDC hdcMEM; //用于緩沖作圖的內(nèi)存DC HBITMAP bmp; //內(nèi)存中承載臨時圖象的位圖 HANDLE hOld; hdcMEM = CreateCompatibleDC(hDC);//依附窗口DC創(chuàng)建兼容內(nèi)存DC bmp = CreateCompatibleBitmap(hDC, 100, 100); //創(chuàng)建與hDC環(huán)境相關的設備兼容的位圖 SelectObject(hdcMEM, bmp); hOld = SelectObject(hdcImage, hBitmap); StretchBlt(hDC, 0, 0, 100, 100, hdcMEM, 0, 0, 100, 100, SRCCOPY); SelectObject(hdcImage, hOld); DeleteObject(hOld); }
但以上代碼似乎沒有用到hBitmap,當然屏幕上也不會有任何輸出,但網(wǎng)上的資料基本一樣。查了一番資料,才明白如果hDC中已經(jīng)有位圖數(shù)據(jù),BitBlt的時候,就會直接把hDC中的數(shù)據(jù)畫到內(nèi)存緩沖區(qū)里。所以,還需要建一DC,名為hdcImage,把要畫的位圖選入內(nèi)存hdcImage中,然后再在內(nèi)存緩沖區(qū)上繪圖。
整理代碼如下:
void DrawBmp(HDC hDC, HBITMAP hBitmap) { HDC hdcImage; HDC hdcMEM; //注意此處,創(chuàng)建了兩個HDC hdcMEM = CreateCompatibleDC(hDC); hdcImage = CreateCompatibleDC(hDC); HBITMAP bmp = ::CreateCompatibleBitmap(hDC, nWidth, nHeight);//創(chuàng)建與hDC環(huán)境相關的設備兼容的位圖 SelectObject(hdcMEM, bmp); SelectObject(hdcImage, hBitmap);//注意此處,將要畫的位圖選入hdcImage StretchBlt(hdcMEM, 0, 0, 100, 100, hdcImage, 0, 0, 100, 100, SRCCOPY); //這里才能正常畫圖,將hdcImage中的位圖直接復制到內(nèi)存緩沖區(qū) StretchBlt(hDC, 0, 0, 100, 100, hdcMEM, 0, 0, 100, 100, SRCCOPY); //再將內(nèi)存緩沖區(qū)中的數(shù)據(jù)繪制到屏幕上. DeleteObject(hdcImage); }
當然,要注意的一點就是,如果要繪制多張圖片,比如兩張,如果大家這樣調(diào)用:
DrawBmp(hDC, hBitmap1); DrawBmp(hDC, hBitmap2);
依然會發(fā)生閃爍,下面解釋原因:
舉個例子,屏幕繪圖就像現(xiàn)場作畫,如果兩次調(diào)用繪圖函數(shù),就相當于在觀眾面前作畫,第一次畫第一張(例如中國象棋的背景)。第二次畫第二張(如棋盤)。這樣,在畫背景和棋盤時,由于顏色有反差,必然在貼第二張圖時會發(fā)現(xiàn)閃爍,這樣利用雙緩沖相當于沒用,還浪費了內(nèi)存空間。
雙緩沖的原理是:在內(nèi)存中先把第一張圖畫好,此時不要轉(zhuǎn)畫到屏幕上,然后繼續(xù)在原來的內(nèi)存中畫第二張,等把所有的圖全畫好后,再一次性貼到屏幕上。那樣內(nèi)存中存在的就是完整的圖形,觀眾看不到繪圖的過程,只能看到繪圖的結果,而最后是一次性復制到屏幕上的,當然不會發(fā)生閃爍現(xiàn)象。
為了更好解釋雙緩沖的原理,附圖片如下:
PS:以上照片來自網(wǎng)絡,只為能更好理解,本人無意侵權。
在以上代碼的基礎上作如下更改:
void DrawBmp(HDC hDC, HBITMAP hBitmap) //此處返回類型改為HDC { HDC hdcMEM; hdcMEM = CreateCompatibleDC(hDC); SelectObject(hdcMEM, hBitmap); //將位圖選擇進內(nèi)存DC StretchBlt(hDC, 0, 0, 100, 100, hdcMEM, 0, 0, 100, 100, SRCCOPY);//這里才能正常畫圖,將hdcImage中的位圖直接復制到內(nèi)存緩沖區(qū) DeleteObject(hdcMEM); }
調(diào)用以上函數(shù)在內(nèi)存中畫第一張圖:
DrawBmp(hdcTmp , hBitmap1);
畫第二張圖
如果要畫多張圖,就依次調(diào)用本函數(shù)繪制,記得一定要把所有的圖全畫到一個設備DC上,最后再一次性畫到屏幕上,才不會出現(xiàn)閃爍現(xiàn)象。
等把所有圖全畫到hdcTmp中后,hdcTmp中已經(jīng)有了完整的圖形,再把完整的圖形繪制到屏幕上:
至此,雙緩沖畫多幅圖繪制完畢。
再給大家一個實例:
void C****Dlg::OnPaint() { if (IsIconic()) { //...... } else { //CDialog::OnPaint(); //不要調(diào)用這個 CPaintDC dc(this);//對話框的dc//通常CPaintDC用來響應WM_PAINT消息。 //CPaintDC是從CDC派生出來的:在構造時自動調(diào)用CWnd::BeginPaint,析構時調(diào)用CWnd::EndPaint。 RECT rect;// 客戶區(qū)矩形 GetClientRect(&rect); // 使用雙緩沖避免屏幕刷新時閃爍 CDC dcMem;// 內(nèi)存dc CBitmap bmpMem; // 位圖 dcMem.CreateCompatibleDC(NULL);// 創(chuàng)建兼容dc bmpMem.CreateCompatibleBitmap(&dc, rect.right-rect.left, rect.bottom-rect.top);//創(chuàng)建跟客戶區(qū)域大小一樣的(空)位圖 // 把位圖選到設備上下文環(huán)境中 CBitmap *pOld = dcMem.SelectObject(&bmpMem); // dcMem.FillSolidRect(&rect, RGB(255,255,255)); // 在此處將繪制內(nèi)容全畫到dcMem內(nèi)存中,(即把之前使用CPaintDC繪制的dc換成dcMem即可) DrawTable(dcMem);//畫棋盤 DrawChesses(dcMem); // 畫棋子 //...... // 至此,內(nèi)存中繪圖完畢 // 從內(nèi)存拷貝到設備dc dc.BitBlt(0, 0, rect.right - rect.left, rect.bottom - rect.top, &dcMem, 0, 0, SRCCOPY); dc.SelectObject(pOld); // 釋放資源 bmpMem.DeleteObject(); dcMem.DeleteDC(); } }
解決方法:
1)添加BOOL類型的成員變量bgroundChanged,初始化為FALSE;
2)在切換背景圖片前調(diào)用ModifyStyle(WS_CLIPCHILDREN, 0)去掉WS_CLIPCHILDREN屬性,并把bgroundChanged設置為TRUE;
3)在OnPaint中最后增加
if (TRUE == bgroundChanged) { bgroundChg = FALSE; ModifyStyle(0, WS_CLIPCHILDREN); }
希望本文能夠?qū)Υ蠹沂炀氄莆针p緩沖問題有所幫助。
相關文章
Qt 使用Poppler實現(xiàn)pdf閱讀器的示例代碼
下面小編就為大家分享一篇Qt 使用Poppler實現(xiàn)pdf閱讀器的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01