+MFC+GDI双缓冲避免图形闪烁
- 格式:pdf
- 大小:228.29 KB
- 文档页数:9
MFC双缓冲双缓冲说⽩就是贴图,将数据全部绘制在缓冲兼容DC上,再将兼容DC的数据⼀次全部绘制在屏幕上。
相较直接在DC上绘图,双缓冲是将绘制数据全部输出,⽽⾮分步绘制,并且,可以避免Windows刷新背景⾊避免闪烁问题。
⽰例如下:双缓冲://在缓冲区作图,最后将缓冲区数据⼀次全部拷贝⾄⽬标DCvoid CDoubleBufferView::DrawEx(CDC* pDC){CRect rect;GetClientRect(rect);//创建兼容当前DC的缓冲DCCDC cacheDC;cacheDC.CreateCompatibleDC(pDC);//创建兼容当前DC的位图CBitmap bitmap;bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());//将位图选⼊到缓冲DCCBitmap* pOldBitmap = cacheDC.SelectObject(&bitmap);//填充缓冲DC的背景(默认是⿊⾊)cacheDC.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(255, 255, 255));//在缓冲DC中画图int x = 0;int y = 0;int step = 2;int idx = 0;while (idx++ < 10000){cacheDC.MoveTo(x, y);cacheDC.LineTo(rect.Width() - x, y);cacheDC.LineTo(rect.Width() - x, rect.Height() - y);x += step;cacheDC.LineTo(x, rect.Height() - y);y += step;}//将缓冲DC输出到当前DCpDC->BitBlt(0, 0, rect.Width(), rect.Height(), &cacheDC, 0, 0, SRCCOPY);//选回旧的bitmapcacheDC.SelectObject(pOldBitmap);//释放资源bitmap.DeleteObject();cacheDC.DeleteDC();}不⽤双缓冲://直接在DC上作图void CDoubleBufferView::Draw(CDC* pDC){CRect rect;GetClientRect(rect);//在缓冲DC中画图int x = 0;int y = 0;int step = 2;int idx = 0;while (idx++ < 10000){pDC->MoveTo(x, y);pDC->LineTo(rect.Width() - x, y);pDC->LineTo(rect.Width() - x, rect.Height() - y);x += step;pDC->LineTo(x, rect.Height() - y);y += step;}}。
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.SetV iewportOrg(-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 defaultSCROLLINFO 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 defaultSCROLLINFO 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 defaultreturn true;return CDialog::OnEraseBkgnd(pDC);}再运行一下,就会发现闪烁不见了.5.总结双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理了一下思路,以供开发人员参考.。
使用双缓存来解决GDI下的闪烁问题
韩丽娜;石昊苏
【期刊名称】《计算机工程与设计》
【年(卷),期】2006(27)17
【摘要】在图形图像处理过程中,当显示绘制的图像时,有时会出现闪烁的情况.本文从窗口、视口、坐标系统的基本概念和关系出发,主要讲解了如何使用双缓存来解决GDI下的闪烁问题.此方法已经应用于项目地质资料解释系统中对井曲线的修改和显示部分,实践证明,这种方法是可行的.
【总页数】3页(P3258-3260)
【作者】韩丽娜;石昊苏
【作者单位】西北大学,信息科学技术学院,陕西,西安,710069;咸阳师范学院,计算机科学系,陕西,咸阳,712100;西北政法学院,信息网络中心,陕西,西安,710063
【正文语种】中文
【中图分类】TP311.11
【相关文献】
1.在GDI+中使用双缓存 [J], 申晓
2.基于双缓存技术解决某模拟系统实时显示屏幕闪烁的方法 [J], 董燕;周燕明;崔卫兵
3.基于双缓存技术解决某模拟系统实时显示屏幕闪烁的方法 [J], 董燕;周燕明;崔卫兵
4.VC6.0下使用GDI+为应用程序添色彩 [J], 费之茵
5.基于双缓存技术解决某模拟系统实时显示屏幕闪烁的方法 [J], 董燕;周燕明;崔卫兵
因版权原因,仅展示原文概要,查看原文内容请购买。
屏幕无闪烁绘图少将在使用VC++编写某些绘图程序时,你可能会发现,窗口在每次重绘时都会发生闪烁。
特别是在绘制较复杂的图像时,这种闪烁将会更加剧烈。
这里介绍一种防止窗口绘图时闪烁的双缓冲技术。
首先,先解释一下什么叫双缓冲。
双缓冲,即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
通过这个简短的解释,你是否对双缓冲有了初步的理解?不理解没关系,继续往下看。
双缓冲实现过程如下:1、在内存中创建与画布一致的缓冲区2、在缓冲区画图3、将缓冲区位图拷贝到当前画布上4、释放内存缓冲区举例:对话框中应用双缓冲绘图编译环境:操作系统Windows XP编译工具Visual C++ v6.01、新建一个基于对话框的应用程序在对话框中添加一个picture控件更改ID为IDC_FIGURE,在需要绘图的类的头文件中定义几个双缓冲用到的变量CRect rect; // 存储绘图控件的绘图区域CDC *pDC; // 控件的屏幕绘图设备指针CDC memDC; // 内存绘图设备CBitmap memBitmap; // 用于内存绘图的位图CBitmap* pOldBmp; // 备份旧的位图指针CWnd* pWnd; // 绘图控件的指针//添加一个成员函数:void DrawFigure(CDC *pDC);2、在对话框的初始化函数OnInitDialog()中添加以下代码pWnd = GetDlgItem(IDC_FIGURE); // 获得对话框上的picture控件的窗口句柄pWnd->GetClientRect(&rect); // 获取绘制区域pDC = pWnd->GetDC(); // 获得对话框上的picture的设备指针pOldBmp = NULL; // 将旧的位图指针置空// 创建内存绘图设备,使内存位图的DC与控件的DC关联memDC.CreateCompatibleDC(pDC);memBitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);pOldBmp = memDC.SelectObject(&memBitmap);SetTimer(1,100,NULL); // 启动定时器3、DrawFigure函数的定义void CMyDialog::DrawFigure(CDC *pDC) //CMyDialog,是需要绘图的对话框类{//声明画笔对象,及画笔指针CPen pen,*oldpen;//画一个矩形区域pDC->Rectangle(&rect);pDC->MoveTo(0,0);int x,y;for(int i =0; i<1000; i++){pen.CreatePen(PS_SOLID,1, RGB(rand()%255,rand()%255,rand()%255)); oldpen = pDC->SelectObject(&pen);x = rand()%rect.Width();y = rand()%rect.Height();pDC->LineTo(x,y);pDC->SelectObject(oldpen);pen.DeleteObject();}}4、添加Paint事件,在Paint事件处理代码中添加代码pDC->BitBlt(rect.left,rect.top,rect.right,rect.bottom,&memDC,0,0,SRCCOPY);5、添加Timer事件,在Timer事件处理函数中添加代码//在位图中画图片DrawFigure(&memDC);//使屏幕刷新OnPaint();6、在Close事件中添加代码,用来删除创建的对象,以释放内存memDC.SelectObject(pOldBmp);memDC.DeleteDC();memBitmap.DeleteObject();至此,对话框中应用双缓冲的简单例子就介绍完毕若在View视图中应用双缓冲,需要将OnEraseBkgnd(CDC* pDC)函数中的返回语句改为return TRUE;从而避免背景不同而形成闪烁。
双缓冲的概念双缓冲是指在计算机图形学中一种常用的技术,用于解决图像绘制过程中的闪烁问题。
在介绍双缓冲概念之前,我们先来了解一下单缓冲。
在单缓冲模式下,图像数据的存储是直接在显示设备上进行,每当进行图像更新时,就会立即将数据写入显示设备,这种方式会造成图像的闪烁现象。
这是因为在绘制复杂的图形或进行频繁的图像更新时,系统无法同时完成图像的计算和数据的显示,导致显示设备在刷新图像时会出现一些残留的影像。
而双缓冲技术通过引入一个额外的缓冲区来解决这个问题。
也就是说,在双缓冲模式下,图像数据的存储是在内存中进行的,而不是直接写入显示设备。
工作的时候,先将要显示的图像绘制在内存中的一个缓冲区里,并不会立即显示在屏幕上,而是先绘制在另一个缓冲区里,等到整个绘制过程完成后,再将整个缓冲区的图像一次性地复制到显示设备上,这样就避免了图像闪烁的问题。
举个例子来说明双缓冲的过程。
假设我们要在屏幕上绘制一个矩形,使用单缓冲模式时,我们首先将矩形绘制在显示设备上,然后绘制其他元素,当需要更新矩形的位置时,我们需要将屏幕上的矩形擦除,重新绘制新位置的矩形,这个过程会导致矩形在移动时出现闪烁。
而在双缓冲模式下,我们首先在内存中创建两个缓冲区,一个是前缓冲区,用于绘制和显示图像,另一个是后缓冲区,用于存储已绘制的图像。
当需要绘制矩形时,我们将矩形绘制在后缓冲区上,然后再将整个后缓冲区的图像复制到前缓冲区,最后再将前缓冲区的图像显示在屏幕上。
当需要更新矩形的位置时,我们只需在后缓冲区上绘制新位置的矩形,然后再次将整个后缓冲区的图像复制到前缓冲区,从而实现了图像的平滑移动,避免了图像闪烁。
双缓冲技术的优点在于避免了图像闪烁问题,提高了图像显示的质量,尤其在需要频繁更新图像或进行复杂图形绘制时效果更为明显。
同时,双缓冲也可以提高图像显示的速度,因为将整个缓冲区的图像复制到显示设备中只需一次操作,减少了对显示设备的频繁写入操作。
然而,双缓冲也会带来一些额外的开销。
利用双缓存技术提高GDI+的绘图效果摘要在进行图像处理的软件中,一般的图形处理程序需要大量的绘图操作,首要解决的就是绘图的效率和效果问题:前者表现在性能方面,是否有延迟,一个小小的交互操作是否要等上几秒才能看到结果;后者表现在重新绘制或刷新的过程中出现闪烁?本文主要介绍GDI+程序中引用双缓冲技术以解决重绘时出现的闪烁问题—提高绘图效果。
关键字:GDI+;双缓存;快速;缓存位图;效果引言在使用GDI+的实际的绘图中,绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。
这样的效率非常低,不仅达不到性能要求,而且还可能会出现闪烁的现象。
刷新过程中会导致所有图元重新绘制,而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。
窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。
所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。
根据以上分析可知,当图元数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。
特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
1、双缓存技术为了解决画图的效率问题,我们引进了双缓存技术,双缓冲技术早在GDI的程序开发中,就已经开始使用。
在普通的绘图模式下,图像处理程序是按照设计的顺序,将被绘制的元素一个接一个地在目标设备上进行绘制。
何谓“双缓冲”?它的基本原理就是:先在内存中开辟一块虚拟画布,然后将所有需要画的图形先画在这块“虚拟画布”上,最后在一次性将整块画布画到真正的窗体上。
[转载]MFC绘制动态曲线,⽤双缓冲绘图技术防闪烁先上效果图随着时间的推移,曲线向右平移,同时X轴的时间坐标跟着更新。
⼀、如何绘制动态曲线 所谓动画,都是⼀帧⼀帧的图像连续呈现在⽤户⾯前形成的。
所以如果你掌握了如何绘制静态曲线,那么学会绘制动态曲线也不远啦,只需要创建⼀个定时器(⽐如调⽤MFC中的SetTimmer函数),每隔⼀定时间(⽐如1ms),调⽤OnPaint或者OnDraw函数,绘制当前帧图像即可。
这⾥需要注意的是,绘制图像的代码需要写在OnPaint或者OnDraw函数中,因为当窗⼝失效(⽐如最⼩化)恢复后,会重新绘制当前窗⼝,窗⼝之前的⾃绘图像会丢失。
⽽把绘图代码写在OnPaint或者OnDraw中就是为了让窗⼝每次重绘时也能重绘你⾃⼰画的图像,避免出现窗⼝最⼩化再恢复后,⾃⼰画的图像丢失的尴尬情况。
另外绘制当前帧图像之前,记得⽤InvalidateRect函数清除上⼀帧图像,不然各帧图像会背景的堆叠。
⽐如我想清除窗⼝中(0,0)和(100,100)这两点确定的矩形中的图像,代码如下:CRect Rect;Rect.top = 0;Rect.left = 0;Rect.bottom = 100;Rect.right = 100;InvalidateRect(Rect); 根据上⾯的思路,我们每隔⼀定时间绘制⼀幅图像,可是如果每次绘制的图像都是完全相同的,那么图像看起来也是静态的。
如何让曲线动起来呢?我们需要为⾃⼰绘图的代码设计⼀个输⼊,即在当前时刻曲线上各个点的坐标信息。
随着时间的推移,令曲线上各个点的坐标随之变化,这样每次绘图都是基于当前时刻的曲线坐标绘制的,控制好曲线坐标的变化,也就能让你绘制的曲线乖乖的动起来。
上⾯提到了曲线上各个点的坐标信息,这个信息可以⽤多种数据结构储存,不过笔者推荐使⽤STL中的deque数据结构储存。
为什么呢?需求决定选择。
让我们先想想在绘制图像的过程中需要对这个数据进⾏哪些操作。
一、 概述这段时间在研究GDI+双缓冲的实现方法,在网上花了很多时间都没有找到合适的示例,特别是针对VC6的。
后来通过对网上资料的分析,和对SDK/MFC的学习,实现了VC6下的GDI+双缓冲,把它写出来与大家分享,希望可以找到更好的实现方法。
GDI+的一个优点就是可以直接使用jpg图像,在这个示例中,我没有把图像文件放在资源中,而是动态读取,一是由于项目需求,图像数目是未知的;二是由于动态读文件效率低下,使用双缓冲更能体现出优势。
二、 分析在实现过程中,犯了一个错误,把读图像的方法放在了OnDraw之中,后来发现拖动滚动条闪烁的很严重,原因在于ScrollBar拖动时会产生消息激活OnDraw,这样的话,每拖动滚动条就读一次文件,重绘一次,效率当然低下了!我的实现方式是:更换图像后调用UpdateAllViews,内存DC的绘制都放在OnUpdate中操作,在内存绘制好后再在屏幕中贴图。
view sourceprint?1.// 贴上画布2.m_pOldBitmap = m_memDC.SelectObject(&m_memBitmap);3.m_memDC.FillSolidRect(0,0,2000,2000,RGB(100,100,100)); view sourceprint?01.// 贴图02.CString pStrFullPath = pDoc->m_pStrMapPath + pDoc->m_pStrMapName;ES_CONVERSION;04.LPWSTR wStr = A2W(pStrFullPath);05.Image img(wStr);06.pDoc->m_nMapWidth = img.GetWidth();07.pDoc->m_nMapHeight = img.GetHeight();08.Graphics g(m_memDC.GetSafeHdc());09.g.DrawImage(&img,0,0,img.GetWidth(),img.GetHeight()); view sourceprint?1.// 恢复2.m_memDC.SelectObject(m_pOldBitmap);三、截图图一 GDI+双缓冲效果四、结束语如果大家有更好的方法,请写出来分享。
一、闪烁问题。
闪烁问题在MFC窗体中经常见到。
在网上碰到这些问题的层次不穷,解决方法也是多样的!但是最经典也最耐用的还是靠用双缓存解决!首先让我们来了解一下,为什么会产生屏幕闪烁问题:闪烁可以这样定义:当后面一幅图像以很快的速度画在前面一幅图像上时,在后面图像显示前,你可以很快看到前面那一个图像,这样的现象就是闪烁。
我认为,闪烁会让使用者对程序很不满,原因是:如果用户接口编码如此糟糕,那么程序的其他部分呢,如何能相信数据的正确性呢?一个具有平滑,快速相应的程序会给用户带来信心,这个道理很简单。
程序出现闪烁可以由多种形式造成,最常见的原因是窗口大小发生改变时,其内容重画造成闪烁。
仅仅画一次这是一个黄金法则,在任何计算机(Windows或者你使用的任何操作系统)上处理画法逻辑都需要遵循,即永远不要将同一像素画两次。
一个懒惰的程序员常常不愿意在画法逻辑上投入过多精力,而是采用简单的处理逻辑。
要避免闪烁,就需要确保不会出现重复绘制的情况发生。
现在,WIndows和计算机还是很笨的,除非你给他们指令,否则他们不会做任何事情。
如果闪烁的现象发生,那是因为你的程序刻意地多绘制了屏幕的某些区域造成的. 这个现象可能是因为一些明确的命令,或者一些被你忽视了的地方。
如果程序有闪烁的现象出现,你需要你知道如何找到好的方案去解决这个问题。
WM_ERASEBKGND通常,首先需要怀疑的是WM_ERASEBKGND消息。
当一个窗口的背景需要被擦除时,这个消息会被发送。
这是因为窗口的绘画通常经历了两个过程WM_ERASEBKGND: 清除背景WM_PAINT: 在上面绘制内容这两个过程让窗体在绘制内容时变得很简单,即:每次当收到WM_PAINT消息时,你知道已经有了一个新画布等待去绘制。
然而,画窗口两次(一次是通过WM_ERASEBKGND画背景,另外一次是WM_PAINT)将会导致窗口出现比较糟糕的闪烁现象。
只要看看标准的编辑框-打开Windows的写字板并改变窗口大小,就可以看到那种闪烁的效果。
clistctrl excludecliprect闪烁-回复在描述问题之前,让我们先了解几个相关的概念。
首先,`CListCtrl`是一个MFC库中的控件,用于显示列表数据,并提供了许多自定义的功能。
其次,`ExcludeClipRect`是一个Windows图形设备接口(GDI)函数,用于绘制时排除特定的矩形区域,以减少不必要的绘制,提高性能。
接下来,我们将探讨`CListCtrl`和`ExcludeClipRect`之间的关系,并探索如何使用后者来解决可能出现的闪烁问题。
当我们在`CListCtrl`中显示大量的数据时,我们可能会遇到闪烁问题。
这是由于频繁绘制数据而导致的。
默认情况下,`CListCtrl`在滚动或更新时会触发绘制操作,并且在某些情况下,这可能会导致频繁的重绘,进而引发闪烁。
在这种情况下,我们可以使用`ExcludeClipRect`函数来优化绘制过程,减少闪烁的可能性。
首先,我们需要了解`CListCtrl`如何触发绘制操作。
`CListCtrl`有一个消息处理函数`OnPaint`,该函数在窗口无效或收到绘制消息时被调用。
默认情况下,当我们更新列表控件中的数据、滚动或改变列表控件的大小时,`CListCtrl`会标记自身为无效,以便在下次绘制消息到达时重新绘制。
然而,这种标记为无效的机制可能会导致频繁的重绘,从而产生闪烁。
为了避免闪烁,我们可以在绘制过程中利用`ExcludeClipRect`函数排除掉不需要绘制的区域。
这样,我们就可以只绘制可见的部分,而忽略掉不可见的部分。
这种方式不仅减少了绘制的工作量,而且降低了重绘的频率,从而改善了用户体验。
要使用`ExcludeClipRect`函数,我们需要知道要排除的矩形区域的坐标。
在使用`CListCtrl`时,我们可以利用其提供的`GetClientRect`函数获取列表控件的客户区域,然后使用`GetFirstVisibleIndex`和`GetLastVisibleIndex`函数获取可见项目的索引。
GDI+绘图闪烁解决⽅法闲着没事,准备做⼀个类似于TeeChart的⾃定义控件,结果第⼀步的绘图就把我给难倒了,虽然早就知道GDI绘图的闪烁问题很坑,但是却没有想到如此之坑,折腾了两天,才找到解决⽅法。
⾸先在窗体加载的时候,加⼊双缓存,说实话以前⼀直没觉得这个双缓存有什么⽤,不过这次总算是有了点⼉效果。
DoubleBuffered = true;SetStyle(ControlStyles.OptimizedDoubleBuffer |ControlStyles.ResizeRedraw |ControlStyles.AllPaintingInWmPaint, true);下⾯是⼀个例⼦,我准备画⼀个简单的坐标系。
这个⽅法会获得⼀个⾃定义的位图,将这个位图直接赋值给窗体的背景图⽚,或者PictureBox控件也可以。
///<summary>///绘制界⾯///</summary>private Bitmap DrawGDI(){var bmp = new Bitmap(Width, Height);var pen = new Pen(Color.Black, 1.0f);Graphics g = Graphics.FromImage(bmp);var p1 = new Point(20, 10);var p2 = new Point(20, Height - 25);var p3 = new Point(Width - 10, Height - 25);g.DrawLines(pen, new[] { p1, p2, p3 });g.DrawString("0", Font, Brushes.Black, 10, Height - 20);return bmp;}我这⾥是直接在重绘⽅法⾥⾯给背景图⽚直接赋值了。
///<summary>///重绘///</summary>protected override void OnPaint(PaintEventArgs e){BackgroundImage = DrawGDI();base.OnPaint(e);}做完这些之后,我惊讶的发现GDI绘制出的图居然不闪了。