带滚动条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
.总结
双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是
在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有
统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理
了一下思路,以供开发人员参考.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值