MFC双缓冲画图
- 格式:doc
- 大小:30.00 KB
- 文档页数:4
首页 > MFC编程 > 双缓冲区绘图操作的实现双缓冲区绘图操作的实现2010年12月31日代码疯子发表评论阅读评论在图形图象处理编程过程中,双缓冲是一种基本的技术。
我们知道,如果窗体在响应WM_PAINT消息的时候要进行复杂的图形处理,那么窗体在重绘时由于过频的刷新而引起闪烁现象。
解决这一问题的有效方法就是双缓冲技术。
因为窗体在刷新时,总要有一个擦除原来图像的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,然后在调用新的绘图代码进行重绘,这样一擦一写造成了图像颜色的反差。
当WM_PAINT的响应很频繁的时候,这种反差也就越发明显,于是我们就看到了闪烁现象。
我们会很自然的想到,避免背景色的填充是最直接的办法。
但是那样的话,窗体上会变的一团糟。
因为每次绘制图像的时候都没有将原来的图像清除,造成了图像的残留,于是窗体重绘时,画面往往会变的乱七八糟。
所以单纯的禁止背景重绘是不够的。
我们还要进行重新绘图,但要求速度很快,于是我们想到了使用BitBlt函数。
它可以支持图形块的复制,速度很快。
我们可以先在内存中作图,然后用此函数将做好的图复制到前台,同时禁止背景刷新,这样就消除了闪烁。
以上也就是双缓冲绘图的基本的思路。
下面来看一个简单的例子,在CProjectNameView的OnDraw函数进行稍微复杂的绘图操作(复杂一点闪烁效果明显)1 2 3 4 5 6 7 8 9 10 11 12//普通的画图操作C P o i n t p t C e n t e r;C R e c t r e c t,e l l i p s e R e c t;G e t C l i e n t R e c t(&r e c t);p t C e n t e r=r e c t.C e n t e r P o i n t();f o r(i n t i=60;i>0;--i){e l l i p s e R e c t.S e t R e c t(p t C e n t e r,p t C e n t e r);e l l i p s e R e c t.I nf l a t e R e c t(i*5,i*5);p D C->E l l i p s e(e l l i p s e R e c t);}下面是双缓冲区的绘图代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25//双缓冲区画图C P o i n t p t C e n t e r;C R e c t r e c t,e l l i p s e R e c t;G e t C l i e n t R e c t(&r e c t);p t C e n t e r=r e c t.C e n t e r P o i n t();C D C d c M e m;//用于缓冲作图的内存C DC B i t m a p b m p;//内存中存在临时图像的位图d c Me m.C r e a t e C o m p a t i b l e D C(p D C);//依附窗口D C创建兼容D C//创建兼容位图b m p.C r e a t e C o m p a t i b l e B i t m a p(&dc M e m,r e c t.W id t h(),re c t.H e i g h t());d c Me m.S e l e c t O b j e c t(&b m p);//将位图选入内存D Cd c Me m.F i l l S o l i d R e c t(r e c t,p D C->G e t B k C o l o r());//按照原有背景色填充客户区//绘图操作f o r(i n t i=60;i>0;--i){e l l i p s e R e c t.S e t R e c t(p t C e n t e r,p t C e n t e r);e l l i p s e R e c t.I nf l a t e R e c t(i*5,i*5);d c Me m.E l l i p s e(e l l i p s e R e c t);//在内存D C上绘图}//将内存D C上的东西复制到p D Cp D C->B i t B l t(0,0,r e c t.W i d t h(),r e c t.H e i g h t(),&d c M e m,0,0,S R C C O P Y);d c Me m.D e l e t e D C();//删除D Cb m p.D e l e t e O b j ec t();//删除位图更多可以看到,当拖动窗口大小时,使用普通绘图操作会不停的闪烁,而双缓冲区的几乎看不到闪烁。
C# WinForm利用GDI+的双缓冲技术来进步绘图效率前言进入.NET时代,Windows的绘图技术也从GDI晋级到了GDI+,从名字就能知道GDI+是对以前传统GDI 绘图技术的一次晋级,不过在微软几乎把所有的新技术都冠之.NET的情况下,GDI+竟然不叫做,还真让我感到有点意外了。
:〕GDI+在一种与设备无关的环境下提供了一套统一的绘图编程模型,极大的进步了Windows绘图编程的方便性,我们再也不用创立什么各种各样复杂的设备环境了,说实话,我如今想起来都头疼。
题归正传,关于如何进展GDI+的根本编程,我不能过多的加以描绘,假如有对此概念还不太清楚的朋友,建议先去理解一下相关的资料,我们在这里主要讨论的是一种进步绘图效率〔主要是动画效率〕的双缓冲技术在GDI+中的应用和实现。
实现目的为了能清楚的比照应用双缓冲技术前后的效果,我编写了一段程序来进展测试。
首先,我创立了一个普通的Windows Application,在主Form中,我放置了一个定时器:timer1,然后将它的Interval属性设置为10,然后在Form上放置两个按纽,分别用来控制定时器的开启和关闭,最后,我还放置了一个label控件,用来显示绘图的帧数。
测试程序在timer1的timer1_Tick事件中,我写下了如下的代码(其中flag是一个bool型标志变量):DateTime t1 = DateTime.Now;Graphics g = this.CreateGraphics();if(flag){brush = new LinearGradientBrush(new PointF(0.0f, 0.0f),new PointF(700.0f, 300.0f), Color.Red, Color.Blue);flag = false;}else{brush = new LinearGradientBrush(new PointF(0.0f, 0.0f),new PointF(700.0f, 300.0f), Color.Blue, Color.Red);flag = true;}for(int j = 0; j < 60; j ++){for(int i = 0; i < 60; i++){g.FillEllipse(brush, i * 10, j * 10, 10, 10);}}DateTime t2 = DateTime.Now;TimeSpan sp = t2 - t1;float per = 1000 / liseconds;运行后,我点击“开场〞按纽,效果如下列图所示:应用双缓冲以前的效果图〔帧数:5帧/秒〕正如大家所看到的,我在程序中使用循环画了几百个圆形,然后在每次的定时器脉冲事件中使用不同方向的线性渐变来对它们进展填充,形成了一个动画效果。
1创建画笔,画刷void CGraphView::OnDraw(CDC* pDC){CGraphDoc* pDoc = GetDocument( );ASSERT_V ALID(pDoc);// TODO: add draw code for native data here// 使用画笔第一步:创建一支新画笔CPen pen(PS_SOLID, 1, RGB(255,0,0));CPen pen(PS_DASHDOT, 1, RGB(255,0,0));//第二步:选择新画笔到设备环境中,同时保存旧画笔CPen * pOldPen = pDC->SelectObject(&pen);// 使用画刷第一步:创建一支新画刷CBrush brush(RGB(255,0,0)); //创建实体画刷CBrush brush(HS_CROSS, RGB(255,0,0)); //创建阴影模式画刷//第二步:选择新画刷到设备环境中,同时保存旧画刷CBrush * pOldBrush = pDC->SelectObject(&brush);// 常用的绘图函数////绘制一个彩色点pDC->TextOut(20, 20, "点");pDC->SetPixel(100, 40, RGB(255,0,0));//绘制直线pDC->TextOut(320, 20,"线段");pDC->MoveTo(400, 40);pDC->LineTo(500, 40);//绘制折线pDC->TextOut(20, 170, "折线");POINT polyline[4]={{240,240},{80,120},{240,120},{80,240}}; pDC->Polyline(polyline,4);//绘制矩形pDC->TextOut(320, 170, "矩形");pDC->Rectangle(390, 110, 600, 230);//绘制椭圆pDC->TextOut(20, 320, "椭圆");pDC->Ellipse(80, 260, 280, 380);//绘制多边形pDC->TextOut(320, 320, "多边形");POINT polygon[3]={{380,330},{530,260},{500,360}};pDC->Polygon(polygon,3);//第三步:恢复设备环境的旧画笔,以便释放新画笔// pDC->SelectObject(pOldPen);//第三步:恢复设备环境的旧画刷,以便释放新画刷// pDC->SelectObject(pOldBrush);}2创建字体的两种方法第一种创建字体的方法:使用CreateFont函数,如下定义:CFont::CreateFontBOOL CreateFont( int nHeight, //字体的高度int nWidth, //字体的宽度int nEscapement, //字体显示的角度int nOrientation, //字体的角度int nWeight, //字体的磅数BYTE bItalic, //斜体字体BYTE bUnderline, //带下划线的字体BYTE cStrikeOut, //带删除线的字体BYTE nCharSet, //所需的字符集BYTE nOutPrecision, //输出的精度BYTE nClipPrecision, //裁减的精度BYTE nQuality, //逻辑字体与输出设备的实际//字体之间的精度BYTE nPitchAndFamily, //字体间距和字体集LPCTSTR lpszFacename //字体名称);例子:font.CreateFont(12, // nHeight0, // nWidth0, // nEscapement0, // nOrientationFW_NORMAL, // nWeightFALSE, // bItalicFALSE, // bUnderline0, // cStrikeOutANSI_CHARSET, // nCharSetOUT_DEFAULT_PRECIS, // nOutPrecisionCLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQualityDEFAULT_PITCH | FF_SWISS, // nPitchAndFamily "Arial"); // lpszFacename一般只修改几项。
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;}}。
双缓冲的意思就是先将图画在内存中,然后再一次拷贝显示到屏幕上画图一般是在OnPaint中画:MFC双缓冲绘图2008-03-25 13:46如何避免闪烁在知道图形显示闪烁的原因之后,对症下药就好办了。
首先当然是去掉MFC提供的背景绘制过程了。
实现的方法很多,* 可以在窗口形成时给窗口的注册类的背景刷付NULL* 也可以在形成以后修改背景static CBrush brush(RGB(255,0,0));SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);* 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,变得一团乱。
怎么办?这就要用到双缓存的方法了。
双缓冲就是除了在屏幕上有图形进行显示以外,在内存中也有图形在绘制。
我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。
这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。
当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。
如何实现双缓冲首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:CDC MemDC; //首先定义一个显示设备对象CBitmap MemBitmap;//定义一个位图对象//随后建立与屏幕显示兼容的内存显示设备MemDC.CreateCompatibleDC(NULL);//这时还不能绘图,因为没有地方画 ^_^//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);//将位图选入到内存显示设备中//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);//先用背景色将位图清除干净,这里我用的是白色作为背景//你也可以用自己应该用的颜色MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));//绘图MemDC.MoveTo(……);MemDC.LineTo(……);//将内存中的图拷贝到屏幕上进行显示pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);//绘图完成后的清理MemBitmap.DeleteObject();MemDC.DeleteDC();BOOL CDataStructureView::OnEraseBkgnd(CDC* pDC){CRect rc;CDC dcMem;GetClientRect(&rc);CBitmap bmp; //内存中承载临时图象的位图dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC//创建兼容位图(必须用pDC创建,否则画出的图形变成黑色)bmp.CreateCompatibleBitmap(pDC,rc.Width(),rc.Height());CBitmap *pOldBit=dcMem.SelectObject(&bmp);//按原来背景填充客户区,不然会是黑色dcMen.FillSolidRect(rc,RGB(255,255,255))//画图,添加你要画图的代码,不过用dcMem画,而不是pDC;......pDC->BitBlt(0,0,rc.Width(),rc.Height(),&dcMem,0,0,SRCCOPY);//将内存DC上的图象拷贝到前台//绘图完成后的清理dcMem.DeleteDC(); //删除DCbmp.DeleteObject(); //删除位图return true;//这里一定要用return true,如果用自动生成的,会调用基类,把画出来的覆盖,就什么结果也没有了view sourceprint?}。
《1》普通绘图就是直接在我们看得到的黑板上绘图《2》双缓冲就是先在一个虚拟的黑板上画完,等用到的时候在把虚拟黑板上的图画复制到我们看得到的黑板上去;利用双缓冲的优点就是能够使画面流畅,可以想象把画好的图直接粘贴到黑板上一定比在黑板上重新画要快的多。
——————————————————————开始第一步:新建一个对话框工程第二步:添加两个按钮:一个命名为双缓冲绘图;一个命名为普通绘图;第三步:声明变量:在CMyDlg类上右击添加变量如下:CDC MyDC;CBitmap bmp;CBitmap *oldbmp;首先声明一个与窗口DC兼容的内存DC(MyDC)和两个与内存相兼容的位图(bmp,*oldbmp)第四步:在OnInitDialog()函数中添加以下代码://窗口DCCDC *dc=GetDC();//创建与窗口DC兼容的内存DC(MyDC)及位图(bmp,*oldbmp )MyDC.CreateCompatibleDC(dc);bmp.CreateCompatibleBitmap(dc,200,200);//把内存位图选进内存DC中用来保存在内存DC中绘制的图形oldbmp=MyDC.SelectObject(&bmp);//在内存DC中绘制一些小的圆形,数量要多(体现双缓存的优点)for(int i=0;i<200;i+=6)for(int j=0;j<200;j+=6)MyDC.Ellipse(i-3,j-3,i+3,j+3);第五步:右击CMyDlg类添加windows消息响应函数WM_CLOSE,添加以下代码:MyDC.SelectObject(oldbmp);bmp.DeleteObject();MyDC.DeleteDC();//选进原来的位图,删除内存位图对象和内存DC第六步:双击”双缓冲“按钮添加以下代码:GetDC()->StretchBlt(0,0,200,200,&MyDC,0,0,200,200,SRCCOPY);//把内存DC中的图形粘贴到窗口中;第七步:双击“普通绘图”按钮添加以下代码:for(int i=0;i<200;i+=6)for(int j=0;j<200;j+=6)GetDC()->Ellipse(i-3,j-3,i+3,j+3);//按普通方式在窗口中绘制和在内存DC中一样数量和大小的位图;第八步:运行程序............................先单击普通绘图按钮,大家可以看到绘图的速度有点慢再单击双缓冲绘图按钮,图像马上就显示出来了,这就是双缓冲和普通绘图的区别了______________________________________________________________________完成双缓冲技术说起来也没有那么神秘,举个形象一点的例子吧,有两张纸A和B,纸A代表屏幕,纸B代表后台缓冲,我们将所有的绘图操作都显示在纸B上,然后将纸B覆盖在纸A上,这样体现在纸A上的操作就是绘制了整张图,体现在纸B上的就是纷繁复杂的绘图操作。
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.总结双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理了一下思路,以供开发人员参考.。
winform双缓冲,从内存画图到到picturebox base.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);private Image DrawImage(string strFilePath){try{Bitmap bitmap = new Bitmap(strFilePath);//如果原图⽚是索引像素格式之列的,则需要转换if (IsPixelFormatIndexed(bitmap.PixelFormat)){Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);using (Graphics g = Graphics.FromImage(bmp)){g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;positingQuality = positingQuality.HighQuality;g.DrawImage(bitmap, 0, 0);Pen pen = new Pen(Color.Red, 3);for (int i = 0; i < bitmap.Width; i += bitmap.Width / 4){g.DrawLine(pen, i, 0, i, bitmap.Height);}for (int i = 0; i < bitmap.Height; i += bitmap.Height / 4){g.DrawLine(pen, 0, i, bitmap.Width, i);}}}else{Graphics g = Graphics.FromImage(bitmap);g.DrawImage(bitmap, bitmap.Width, bitmap.Height);Pen pen = new Pen(Color.Red, 3);for (int i = 0; i < bitmap.Width; i += bitmap.Width / 4){g.DrawLine(pen, i, 0, i, bitmap.Height);}for (int i = 0; i < bitmap.Height; i += bitmap.Height / 4){g.DrawLine(pen, 0, i, bitmap.Width, i);}}return bitmap;}catch { return null; }}///<summary>///会产⽣graphics异常的PixelFormat///</summary>private static PixelFormat[] indexedPixelFormats = { PixelFormat.Undefined, PixelFormat.DontCare,PixelFormat.Format16bppArgb1555, PixelFormat.Format1bppIndexed, PixelFormat.Format4bppIndexed,PixelFormat.Format8bppIndexed};///<summary>///判断图⽚的PixelFormat 是否在引发异常的 PixelFormat 之中///</summary>///<param name="imgPixelFormat">原图⽚的PixelFormat</param>///<returns></returns>private static bool IsPixelFormatIndexed(PixelFormat imgPixelFormat){foreach (PixelFormat pf in indexedPixelFormats){if (pf.Equals(imgPixelFormat)) return true;}return false;}下⾯这个函数从内存画图到picbox, 因为有些图⽚是索引⾊, 所以先鉴定了下图⽚. 画图时⽤线画了格⼦. 不⽤时可以去了.。
在stdafx.h中添加的头文件(参照项目OpenGL)//////////////////////////////////////////////////////////////////////////// // 包含有关OpenGL函数的头文件#include<gl/gl.h>#include<gl/glu.h>#include<gl/glaux.h>#include<gl/glut.h>////////////////////////////////////////////////////////////////////////////项目-属性-链接器-输入 Opengl32.lib;glu32.lib;glaux.lib添加的变量和函数:BOOL RenderScene();BOOL SetupPixelFormat(void);void SetLogicalPalette(void);BOOL InitializeOpenGL(CDC* pDC);HGLRC m_hRC; //OpenGL绘制描述表HPALETTE m_hPalette; //OpenGL调色板CDC* m_pDC; //OpenGL设备描述表添加的消息映射:ON_WM_CREATE()ON_WM_DESTROY()ON_WM_SIZE()ON_WM_TIMER()◎PreCreateWindow(CREATESTRUCT& cs)//设置窗口类型cs.style |=WS_CLIPCHILDREN | WS_CLIPSIBLINGS;WS_CLIPCHILDREN,使得父窗体在绘制时留出其上的子窗体的位置不去画它,而那片区域留WS_CLIPSIBLING,必须用于子窗体,使得该子窗体在收到WM_PAINT时同时令其共父的诸多子窗体也会被PAINT。
①OnCreate(LPCREATESTRUCT lpCreateStruct)//初始化OpenGL和设置定时器m_pDC = new CClientDC(this);//设备描述表赋值SetTimer(1, 500, NULL);InitializeOpenGL(m_pDC); //涵盖了glutInit ()和glutInitDisplayMode()的功能///////////////////////////////////////////////////////m_pDC = new CClientDC(this);//设备描述表赋值//SetTimer(1, 500, NULL);InitializeOpenGL(m_pDC); //涵盖了glutInit ()和glutInitDisplayMode()的功能/////////////////////////////////////////////////////////////////////////////②OnSize(UINT nType, int cx, int cy)// 相当于GLUT中的glutReshapeFunc()函数//设置投影变换矩阵,设置视窗glMatrixMode(GL_PROJECTION); //设置当前矩阵为投影矩阵glLoadIdentity();//用单位矩阵初始化当前矩阵glFrustum(-1.0,1.0,-1.0,1.0,3.0,7.0);glViewport(0,0,cx,cy);////////////////////////////////////////////glViewport(0, 0, cx, cy);glMatrixMode(GL_PROJECTION);glLoadIdentity();if ((GLfloat)cx <= (GLfloat)cy)glOrtho(-35, 35, -35 * (GLfloat)cy / (GLfloat)cx, 35 * (GLfloat)cy / (GLfloat)cx, -50, 50);elseglOrtho(-35 * (GLfloat)cx / (GLfloat)cy, 35 * (GLfloat)cx / (GLfloat)cy, -35, 35, -50, 50);////////////////////////////////////③OnDraw(CDC* pDC)glClearColor(0.8f,0.8f,0.0f,1.0f);glClearDepth(1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glMatrixMode(GL_MODELVIEW);//设置当前矩阵为模型矩阵glLoadIdentity();//用单位矩阵初始化当前矩阵RenderScene(); //渲染场景SwapBuffers(wglGetCurrentDC());//当窗口需要被重绘时调用绘制的图形相当于glutDisplayFunc(display);glClear()语句的作用是用当前缓冲区清除值,也就是glClearColor或者glClearD epth等函数所指定的值来清除指定的缓冲区。
[转载]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数据结构储存。
为什么呢?需求决定选择。
让我们先想想在绘制图像的过程中需要对这个数据进⾏哪些操作。
利用定时器和双缓冲技术在MFC中绘制动画作者:杜小甫来源:《数字技术与应用》2013年第08期摘要:对MFC中动画绘制涉及到的两个重要技术做较全面的总结和提炼,创建了一个通用性较强的动画绘制程序框架。
首先对MFC中定时器技术和双缓冲技术做出深入分析;其次创建一个动画绘制程序框架;最后通过几个动画程序验证该框架。
该程序框架已经应用在实际工作中,证明该框架是精炼有效的。
关键词:MFC 动画定时器双缓冲中图分类号:TP311.11 文献标识码:A 文章编号:1007-9416(2013)08-0129-011 定时器和双缓冲技术MFC是微软公司推出的软件开发架构[1],在MFC中绘制动画常见于各类软件开发。
动画绘制一种思路是利用循环语句加延迟函数的方式[2],另外一种思路就是利用定时器[3]定时更像图片绘制动画。
后者在绘制过程中可以响应其他事件,因此应用十分广泛。
1.1 定时器技术定时器可以向系统定时发送信号,触发OnTimer函数。
在定时器使用过程中涉及到三个常用函数,分别对应着使用定时器的三个步骤。
(1)创建定时器。
创建定时器使用SetTimer函数。
MFC中提供两个SetTimer函数,一个是全局函数,可以在程序的任意位置调用。
我们更常使用的是第二种SetTimer函数,由CWnd类重载。
函数有三个参数,分别是定时器编号、时间间隔和回调函数[4]地址。
(2)处理定时器信号。
MFC在OnTimer函数中处理定时器信号。
OnTimer函数具有一个参数,是捕捉到的定时器编号。
我们可以利用其区分不同的定时器信号,执行不同代码。
(3)销毁定时器。
定时器也会占用一定的系统资源,所以必须及时销毁不用的定时器,否则会影响系统运行效率。
MFC中使用KillTimer函数销毁定时器,该函数参数就是待销毁的定时器编号。
1.2 双缓冲技术在Windows平台上,应用程序的图像设备接口被抽象化为设备上下文(Device Content,DC)。
双缓冲(DoubleBuffer)原理和使⽤⼀、双缓冲作⽤双缓冲甚⾄是多缓冲,在许多情况下都很有⽤。
⼀般需要使⽤双缓冲区的地⽅都是由于“⽣产者”和“消费者”供需不⼀致所造成的。
这样的情况在很多地⽅后可能会发⽣,使⽤多缓冲可以很好的解决。
我举⼏个常见的例⼦:例 1. 在⽹络传输过程中数据的接收,有时可能数据来的太快来不及接收导致数据丢失。
这是由于“发送者”和“接收者”速度不⼀致所致,在他们之间安排⼀个或多个缓冲区来存放来不及接收的数据,让速度较慢的“接收者”可以慢慢地取完数据不⾄于丢失。
例2. 再如,计算机中的三级缓存结构:外存(硬盘)、内存、⾼速缓存(介于CPU和内存之间,可能由多级)。
从左到右他们的存储容量不断减⼩,但速度不断提升,当然价格也是越来越贵。
作为“⽣产者”的 CPU 处理速度很快,⽽内存存取速度相对CPU较慢,如果直接在内存中存取数据,他们的速度不⼀致会导致 CPU 能⼒下降。
因此在他们之间⼜增加的⾼速缓存来作为缓冲区平衡⼆者速度上的差异。
例3. 在图形图像显⽰过程中,计算机从显⽰缓冲区取数据然后显⽰,很多图形的操作都很复杂需要⼤量的计算,很难访问⼀次显⽰缓冲区就能写⼊待显⽰的完整图形数据,通常需要多次访问显⽰缓冲区,每次访问时写⼊最新计算的图形数据。
⽽这样造成的后果是⼀个需要复杂计算的图形,你看到的效果可能是⼀部分⼀部分地显⽰出来的,造成很⼤的闪烁不连贯。
⽽使⽤双缓冲,可以使你先将计算的中间结果存放在另⼀个缓冲区中,但全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据⼀次性复制到显⽰缓冲区。
例1 中使⽤双缓冲是为了防⽌数据丢失,例2 中使⽤双缓冲是为了提⾼ CPU 的处理效率,⽽例3使⽤双缓冲是为了防⽌显⽰图形时的闪烁延迟等不良体验。
⼆、双缓冲原理这⾥,主要以双缓冲在图形图像显⽰中的应⽤做说明。
上⾯例3中提到了双缓冲的主要原理,这⾥通过⼀个图再次理解⼀下:图 1 双缓冲⽰意图注意,显⽰缓冲区是和显⽰器⼀起的,显⽰器只负责从显⽰缓冲区取数据显⽰。
在实现绘图的过程中,显示的图形总是会闪烁,笔者曾经被这个问题折磨了好久,通过向高手请教,搜索资料,问题基本解决,现将文档整理出来以供大家参考.1.显示的图形为什么会闪烁我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。
当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。
如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。
当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。
有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。
例如在OnDraw(CDC *pDC)中这样写:pDC->MoveTo(0,0);pDC->LineTo(100,100);这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见闪烁。
其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。
比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画只会闪烁一次。
这个也可以试验,在OnDraw(CDC *pDC)中这样写:for(int i=0;i<100000;i++){pDC->MoveTo(0,i);pDC->LineTo(1000,i);}程序有点极端,但是能说明问题。
说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要闪得厉害一些,但是闪烁频率要低。
那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。
因为动画的连续两个帧之间的差异很小所以看起来不闪。
如果不信,可以在动画的每一帧中间加一张纯白的帧,不闪才怪呢。
2、如何避免闪烁在知道图形显示闪烁的原因之后,对症下药就好办了。
首先当然是去掉MFC提供的背景绘制过程了。
实现的方法很多:* 可以在窗口形成时给窗口的注册类的背景刷付NULL* 也可以在形成以后修改背景static CBrush brush(RGB(255,0,0));SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);* 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE(屏蔽掉系统的自动刷新)这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,变得一团乱。
怎么办?这就要用到双缓存的方法了。
双缓冲就是除了在屏幕上有图形进行显示以外,在内存中也有图形在绘制。
我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。
这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。
当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。
3、如何实现双缓冲首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:(前提是要屏蔽掉OnEraseBkgnd()的刷新,返回TRUE;)CDC MemDC; //首先定义一个显示设备对象CBitmap MemBitmap;//定义一个位图对象//随后建立与屏幕显示兼容的内存显示设备MemDC.CreateCompatibleDC(NULL);//这时还不能绘图,因为没有地方画//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);//将位图选入到内存显示设备中//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);//先用背景色将位图清除干净,这里我用的是白色作为背景//你也可以用自己应该用的颜色MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));//绘图MemDC.MoveTo(……);MemDC.LineTo(……);//将内存中的图拷贝到屏幕上进行显示pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);//绘图完成后的清理MemBitmap.DeleteObject();MemDC.DeleteDC();请参阅注释。
4、如何提高绘图的效率实际上,在OnDraw (CDC *pDC)中绘制的图并不是所有都显示了的,例如:你在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。
裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。
因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。
因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。
但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。
可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。
具体程序大家可看以下源代码:5、无闪烁背景图的绘制代码实现/////////////// 无闪烁背景图绘制 ////////////// 程序设计: icemen (温冰) 树爱兵//////////////////////////////////////////////BOOL CStrucView::OnEraseBkgnd(CDC* pDC){ int nWidth;int nHeight;//CView::OnEraseBkgnd(pDC);CStrucDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);CRect rect;GetWindowRect(&rect);nWidth = rect.Width();nHeight= rect.Height();CDC MemDC;CBitmap MemBitmap;MemDC.CreateCompatibleDC (NULL);MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));/////////////////////////////////////////////////////////// 以上为画背景色 ////////////////// 以下为画背景图 /////////////////////////////////////////////////////////////////////GetClientRect(rect);BITMAP bm;CDC dcMem;VERIFY(m_bmp.GetObject(sizeof(bm),(LPVOID)&bm));dcMem.CreateCompatibleDC(pDC);CBitmap *pOldBMP =( CBitmap *)dcMem.SelectObject(&m_bmp);MemDC.BitBlt( (rect.right - bm.bmWidth)/2,(rect.bottom - bm.bmHeight)/2,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);dcMem.SelectObject(pOldBMP);/////////////////////////////////////////////////////////// 以上为画背景图 /////////////////////////////////////////////////////////////////////pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);MemBitmap.DeleteObject();MemDC.DeleteDC();return TRUE;}/////////////// 无闪烁背景图绘制 ///////////////////////////////////////////////////////注:程序中m_bmp 为相应的 StrucView.h中定义,为位图资源类protected:CBitmap m_bmp;并应加上下面段,当然,你可在任何时候加入loadBitmap 子例程,也可加入文件资源,那由得你了!CStrucView::CStrucView(){VERIFY(m_bmp.LoadBitmap(IDB_BITMAP3));。