帶滾動條VC 雙緩存技術的

VC中的繪圖有個比較棘手的問題是閃爍,雙緩存是解決此類問題的一種方法,但是在系統
繪圖中,由於可能要加載滾動條,響應鼠標拖動等事件,導致傳統的雙緩存方法不一定適用,
本文提出了一種解決方法能夠用統一的框架內實現滾動條,鼠標圖型拖動,視口轉換以及雙
緩存繪圖.
關鍵字:雙緩存,滾動條,鼠標拖動,
VC,視口轉換
炫麗的軟件效果能增強用戶體驗,用繪圖方法展示動人效果就成爲了必不可少的一個環
節,
VC
提供了非常豐富的繪圖
API
函數庫,例如
GDI+
接口,但是用過這些接口函數的開
發人員應該知道,有個非常頭疼的問題是閃爍.如何解決這個問題,雙緩存是一個非常好的
辦法.
1
.雙緩存原理介紹

VC
中進行繪圖過程處理時,如果圖形刷新很快,經常出現圖形閃爍的現象。利用先
在內存繪製,然後拷貝到屏幕的辦法可以消除屏幕閃爍,具體的方法是先在內存中創建一個
與設備兼容的內存設備上下文,也就是開闢一快內存區來作爲顯示區域,然後在這個內存區
進行繪製圖形。在繪製完成後利用
BitBlt
函數把內存的圖形直接拷貝到屏幕上即可。
2
.雙緩存繪圖實現
前面簡單的介紹了
VC
下雙緩存的原理,在實現這一環節,我們的主要工作是將上面的想
法實現.當面我們所用到的
MFC
應用程序是基於對話框,所以代碼我們當前要放在
OnPaint
函數裏.
voidCTestScrollDlg::OnPaint()
{
CPaintDCdc(this);
intnWidth=1000;
intnHeight=1000;
//
隨後建立與屏幕顯示兼容的內存顯示設備
CDCMemDC;//
首先定義一個顯示設備對象
CBitmapMemBitmap;//
定義一個位圖對象
MemDC.CreateCompatibleDC(NULL);
//
這時還不能繪圖,因爲沒有地方畫
//
下面建立一個與屏幕顯示兼容的位圖,至於位圖的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(&dc,nWidth,nHeight);
//
將位圖選入到內存顯示設備中
//
只有選入了位圖的內存顯示設備纔有地方繪圖,畫到指定的位圖上
MemDC.SelectObject(&MemBitmap);
//
先用背景色將位圖清除乾淨,這裏我用的是白色作爲背景
//
你也可以用自己應該用的顏色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
========================================第2頁========================================
//
繪圖
MemDC.Ellipse(m_nEclipseRect);
//
將內存中的圖拷貝到屏幕上進行顯示
dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);
dc.BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
//
繪圖完成後的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();
CDialog::OnPaint();
}
這裏面有一點要着重講一下.幾個變量的定義:
1

nEclipseRect
這是個矩形框,
CRect
類型,用於表示一個橢圓的四個值
2

m_nHScrollPos
是個
int
類型,用之於水平滾動條,表現當前
X
軸座標值.
3

m_nVScrollPos
是個
int
類型,用之於垂直滾動條,表現當前的
Y
軸座標值
在本程序中,由於繪圖的面積可能會比較大,我們採用了滾動條機制.下面列出來滾動
條代碼.
voidCTestScrollDlg::OnHScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar)
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
SCROLLINFOscrollinfo;
GetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
//this->Invalidate(false);
//InvalidateRect(NULL,TRUE);
switch(nSBCode)
{
caseSB_LEFT:
ScrollWindow((scrollinfo.nPos-scrollinfo.nMin)*10,0);
scrollinfo.nPos=scrollinfo.nMin;
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
caseSB_RIGHT:
ScrollWindow((scrollinfo.nPos-scrollinfo.nMax)*10,0);
scrollinfo.nPos=scrollinfo.nMax;
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
caseSB_LINELEFT:
========================================第3頁========================================
scrollinfo.nPos-=1;
if(scrollinfo.nPos)
{
scrollinfo.nPos=scrollinfo.nMin;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(10,0);
break;
caseSB_LINERIGHT:
scrollinfo.nPos+=1;
if(scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos=scrollinfo.nMax;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(-10,0);
break;
caseSB_PAGELEFT:
scrollinfo.nPos-=5;
if(scrollinfo.nPos)
{
scrollinfo.nPos=scrollinfo.nMin;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(10*5,0);
break;
caseSB_PAGERIGHT:
scrollinfo.nPos+=5;
if(scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos=scrollinfo.nMax;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(-10*5,0);
break;
caseSB_THUMBPOSITION:
break;
caseSB_THUMBTRACK:
ScrollWindow((scrollinfo.nPos-nPos)*10,0);
scrollinfo.nPos=nPos;
========================================第4頁========================================
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
caseSB_ENDSCROLL:
break;
}
m_nHScrollPos=scrollinfo.nPos*10;
CDialog::OnHScroll(nSBCode,nPos,pScrollBar);
}
voidCTestScrollDlg::OnVScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar)
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
SCROLLINFOscrollinfo;
GetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
//this->Invalidate(false);
//InvalidateRect(NULL,TRUE);
switch(nSBCode)
{
caseSB_BOTTOM:
ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMax)*10);
scrollinfo.nPos=scrollinfo.nMax;
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
caseSB_TOP:
ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMin)*10);
scrollinfo.nPos=scrollinfo.nMin;
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
caseSB_LINEUP:
scrollinfo.nPos-=1;
if(scrollinfo.nPos)
{
scrollinfo.nPos=scrollinfo.nMin;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,10);
break;
caseSB_LINEDOWN:
scrollinfo.nPos+=1;
if(scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos=scrollinfo.nMax;
break;
========================================第5頁========================================
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,-10);
break;
caseSB_PAGEUP:
scrollinfo.nPos-=5;
if(scrollinfo.nPos)
{
scrollinfo.nPos=scrollinfo.nMin;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,10*5);
break;
caseSB_PAGEDOWN:
scrollinfo.nPos+=5;
if(scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos=scrollinfo.nMax;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,-10*5);
break;
caseSB_ENDSCROLL:
//MessageBox("SB_ENDSCROLL");
break;
caseSB_THUMBPOSITION:
//ScrollWindow(0,(scrollinfo.nPos-nPos)*10);
//scrollinfo.nPos=nPos;
//SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
caseSB_THUMBTRACK:
ScrollWindow(0,(scrollinfo.nPos-nPos)*10);
scrollinfo.nPos=nPos;
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
}
m_nVScrollPos=scrollinfo.nPos*10;
CDialog::OnVScroll(nSBCode,nPos,pScrollBar);
}
這段代碼通用性比較強,可以直接粘到應用程序中使用.但是隻要注意,由於在我們的應
用程序中運行到了視口切換的概念,這是本地座標系跟世界座標系之間的切換,前面提到了
========================================第6頁========================================
二個變量用來標記當前座標系的視口值.具體原理若不理解,請參考視口轉換的原理.
3
.鼠標拖動事件實現
前面的代碼運行出來的效果,是一個對話框,然後在(
0

0

200

200
)的位置上出現
一個橢圓,下面的工作,是想通過鼠標拖動這個橢圓,即實現鼠標的智能拖動.下面我們在
對話框中添加鼠標拖動事件.
voidCTestScrollDlg::OnMouseMove(UINTnFlags,CPointpoint)
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
//dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);
//dc.Ellipse(m_nEclipseRect);
intcx=point.x-m_nOrgPoint.x;
intcy=point.y-m_nOrgPoint.y;
m_nOrgPoint=point;
point.x+=m_nHScrollPos;
point.y+=m_nVScrollPos;
CStringstr;
str.Format("x=%d,y=%d",point.x,point.y);
this->SetWindowText(str);
//
判斷當前的點是否在矩形框中
,
同時是否按下鼠標左鍵
if(nFlags&MK_LBUTTON&&m_nEclipseRect.PtInRect(point))
{
m_nEclipseRect.bottom+=cy;
m_nEclipseRect.top+=cy;
m_nEclipseRect.right+=cx;
m_nEclipseRect.left+=cx;
this->Invalidate();
}
CDialog::OnMouseMove(nFlags,point);
}
這裏面有個變量
m_nOrgPoint
,是個類變量,用之於記錄上一次的座標值
.
4
.再閃爍問題解決
不過我們運行這個效果圖後,會發現又出現了閃爍,這個是什麼問題呢,我們不是已經
添加了雙緩存了嗎.
其實這個問題的產生的原因是因爲我們在鼠標拖動時,也進行了
Invalidate
函數,而這個
時候是不需要進行背景重繪,這個問題的解決是通過添加
OnEraseBkgnd
函數.
在當前對話框中通過添加函數嚮導裏,在
Filter
裏選擇
Window
.然後先中
EraseBkgnd

件.
========================================第7頁========================================
BOOLCTestScrollDlg::OnEraseBkgnd(CDC*pDC)
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
returntrue;
returnCDialog::OnEraseBkgnd(pDC);
}
再運行一下,就會發現閃爍不見了.
5
.總結
雙緩存繪圖的問題幾乎困擾過每一個開發者,雖然參考網上信息,能解決這一問題,但是
在添加鼠標事件,添加滾動條事件,如何通過視口切換的方法,顯示一張大圖,目前並沒有
統一的解決辦法,在實際工作,在統一解決這個問題時,也花了一些功夫,在解決後,整理
了一下思路,以供開發人員參考.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章