摘要
VC中的绘图有个比较棘手的问题是闪烁,双缓存是解决此类问题的一种方法,但是在系统绘图中,由于可能要加载滚动条,响应鼠标拖动等事件,导致传统的双缓存方法不一定适用,本文提出了一种解决方法能够用统一的框架内实现滚动条,鼠标图型拖动,视口转换以及双缓存绘图.
关键字:双缓存,滚动条,鼠标拖动,VC,视口转换
炫丽的软件效果能增强用户体验,用绘图方法展示动人效果就成为了必不可少的一个环节,VC提供了非常丰富的绘图API函数库,例如GDI+接口,但是用过这些接口函数的开发人员应该知道,有个非常头疼的问题是闪烁.如何解决这个问题,双缓存是一个非常好的办法.
1.双缓存原理介绍
在VC中进行绘图过程处理时,如果图形刷新很快,经常出现图形闪烁的现象。利用先在内存绘制,然后拷贝到屏幕的办法可以消除屏幕闪烁,具体的方法是先在内存中创建一个与设备兼容的内存设备上下文,也就是开辟一快内存区来作为显示区域,然后在这个内存区进行绘制图形。在绘制完成后利用BitBlt函数把内存的图形直接拷贝到屏幕上即可。
2.双缓存绘图实现
前面简单的介绍了VC下双缓存的原理,在实现这一环节,我们的主要工作是将上面的想法实现.当面我们所用到的MFC应用程序是基于对话框,所以代码我们当前要放在OnPaint函数里.
void CTestScrollDlg::OnPaint()
{
CPaintDC dc(this);
int nWidth = 1000;
int nHeight = 1000;
//随后建立与屏幕显示兼容的内存显示设备
CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(&dc,nWidth,nHeight);
//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
MemDC.SelectObject(&MemBitmap);
//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
//绘图
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轴坐标值
在本程序中,由于绘图的面积可能会比较大,我们采用了滚动条机制.下面列出来滚动条代码.
void CTestScrollDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
SCROLLINFO scrollinfo;
GetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
// this->Invalidate(false);
//InvalidateRect(NULL,TRUE);
switch (nSBCode)
{
case SB_LEFT:
ScrollWindow((scrollinfo.nPos-scrollinfo.nMin)*10,0);
scrollinfo.nPos = scrollinfo.nMin;
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
case SB_RIGHT:
ScrollWindow((scrollinfo.nPos-scrollinfo.nMax)*10,0);
scrollinfo.nPos = scrollinfo.nMax;
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
case SB_LINELEFT:
scrollinfo.nPos -= 1;
if (scrollinfo.nPos)
{
scrollinfo.nPos = scrollinfo.nMin;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(10,0);
break;
case SB_LINERIGHT:
scrollinfo.nPos += 1;
if (scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos = scrollinfo.nMax;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(-10,0);
break;
case SB_PAGELEFT:
scrollinfo.nPos -= 5;
if (scrollinfo.nPos)
{
scrollinfo.nPos = scrollinfo.nMin;
break;
}
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
ScrollWindow(10*5,0);
break;
case SB_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;
case SB_THUMBPOSITION:
break;
case SB_THUMBTRACK:
ScrollWindow((scrollinfo.nPos-nPos)*10,0);
scrollinfo.nPos = nPos;
SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);
break;
case SB_ENDSCROLL:
break;
}
m_nHScrollPos = scrollinfo.nPos*10;
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
void CTestScrollDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
SCROLLINFO scrollinfo;
GetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
// this->Invalidate(false);
//InvalidateRect(NULL,TRUE);
switch (nSBCode)
{
case SB_BOTTOM:
ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMax)*10);
scrollinfo.nPos = scrollinfo.nMax;
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
case SB_TOP:
ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMin)*10);
scrollinfo.nPos = scrollinfo.nMin;
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
case SB_LINEUP:
scrollinfo.nPos -= 1;
if (scrollinfo.nPos)
{
scrollinfo.nPos = scrollinfo.nMin;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,10);
break;
case SB_LINEDOWN:
scrollinfo.nPos += 1;
if (scrollinfo.nPos>scrollinfo.nMax)
{
scrollinfo.nPos = scrollinfo.nMax;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,-10);
break;
case SB_PAGEUP:
scrollinfo.nPos -= 5;
if (scrollinfo.nPos)
{
scrollinfo.nPos = scrollinfo.nMin;
break;
}
SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
ScrollWindow(0,10*5);
break;
case SB_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;
case SB_ENDSCROLL:
// MessageBox("SB_ENDSCROLL");
break;
case SB_THUMBPOSITION:
// ScrollWindow(0,(scrollinfo.nPos-nPos)*10);
// scrollinfo.nPos = nPos;
// SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);
break;
case SB_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);
}
这段代码通用性比较强,可以直接粘到应用程序中使用.但是只要注意,由于在我们的应用程序中运行到了视口切换的概念,这是本地坐标系跟世界坐标系之间的切换,前面提到了二个变量用来标记当前坐标系的视口值.具体原理若不理解,请参考视口转换的原理.
3.鼠标拖动事件实现
前面的代码运行出来的效果,是一个对话框,然后在(0,0,200,200)的位置上出现一个椭圆,下面的工作,是想通过鼠标拖动这个椭圆,即实现鼠标的智能拖动.下面我们在对话框中添加鼠标拖动事件.
void CTestScrollDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);
// dc.Ellipse(m_nEclipseRect);
int cx = point.x-m_nOrgPoint.x;
int cy = point.y - m_nOrgPoint.y;
m_nOrgPoint = point;
point.x += m_nHScrollPos;
point.y += m_nVScrollPos;
CString str;
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事件.
BOOL CTestScrollDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return true;
return CDialog::OnEraseBkgnd(pDC);
}
再运行一下,就会发现闪烁不见了.
5.总结
双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理了一下思路,以供开发人员参考.
分享到:
相关推荐
VC双缓存绘图以及鼠标拖图的问题.pdf VC双缓存绘图以及鼠标拖图的问题.pdf VC双缓存绘图以及鼠标拖图的问题.pdf
vc双缓存无闪烁绘图的例子,好用,已经打包,便于集成到自己的应用中。
利用vc双缓存解决闪烁问题,里面有详细实例。
vc mtc GDI+ 双缓存vc mtc GDI+ 双缓存vc6.0编译通过
一个VC双缓冲绘图例子,解决了屏幕闪烁的缺点,可以绘制鼠标轨迹任意曲线,另外可以实现打开,修改和保存图片等.
GDI绘图GDI绘图GDI绘图GDI绘图GDI绘图GDI绘图GDI绘图GDI绘图
vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc 图形闪烁 双缓存代码vc...
vc 双缓冲 doc 绘图 变化的圆圈 vc 双缓冲 doc 绘图 变化的圆圈 vc 双缓冲 doc 绘图 变化的圆圈
该程序在VC6.0的对话框中利用双缓冲技术实现了无闪烁绘制图像,其中实现部分在OnPaint函数中。
用VC写的绘图程序源码--用VC写的绘图程序源码--用VC写的绘图程序源码
VC6.0 下实现双缓存DC画图简单示例代码------------------------------------
VC下Matcom绘图,希望对需要使用matcom的有所帮助
VC6下的GDI+双缓冲,解决闪烁问题,附有例子源码,一看就会,图像编程过程中非常实用
VC 正弦曲线绘图实例,这个程序主要是绘制人体生命体征曲线,根据脉动绘制出曲线图,所以本程序也可称为人体曲线生成程序。
VC下开源的绘图控件,功能强大,使用稳定方便,在VC平台和VS平台都可以使用,无需破解注册
VC实现滚动视图双缓冲绘图
VC使用GDI+进行绘图
可以作为课程设计的程序,实现简单的绘图,移动图形的功能。 使用vc框架实现。 GHpaint程序的几个重点 程序的基本功能: 程序提供绘图、删除已绘图形、移动已绘图形。 可以选择绘图颜色、形状、线粗。 重点问题:...
VC++6.0利用双缓存技术,解决闪屏问题,代码有详细注释