1、双缓冲技术实现VC绘图 *所有的GDI绘图函数使用的都是逻辑坐标(逻辑范围)*系统默认情况下 物理范围和逻辑范围 是1:1 的对应关系*1. 首先定义类成员: CDC *m_pDC; CDC MemDC; CBitmap MemBitmap; CBitmap *pOldbitmap; LONG xRange; / 逻辑范围,x方向宽度 LONG yRange; / 逻辑范围,y方向高度 LONG nWidht; / 物理范围,x方向宽度 LONG nHeight; / 物理范围,y方向高度2. 在类初始化函数中:m_pDC = this-GetDC(); / 获取设备上下文句柄 CWnd *w
2、nd = GetDlgItem(IDC_SHOWGRAPH); / 获取界面上显示图形的ID控件的句柄wnd-GetWindowRect(&rect); / 获取显示/画图区域大小(物理范围)ScreenToClient(&rect); / 转换为客户区坐标nWidth = rect.Width(); / 显示/画图区域x方向物理宽度nHeight = rect.Height(); / 显示/画图区域y方向物理高度3. 在自定义函数中,设置视口与窗口的比例关系:m_pDC-SetMapMode(MM_ANISOTROPIC); / 注意MM_ANISOTROPIC和MM_ISOTROPIC的区
3、别m_pDC-SetWindowExt(XRange,-yRange); / 设定窗口尺寸范围,画图使用的逻辑范围,实现放大或是缩小,坐标方向和为正向m_pDC-SetViewportExt(nWidth,nHeight); / 设定视口尺寸范围,客户区实际图形显示的区域范围,大小固定m_pDC-SetViewportOrg(rect.left,rect.bottom); /设定画图的逻辑原点坐标(0,0)在物理坐标的(rect.left,rect.bottom)点上4. 在自定义函数中,双缓冲技术的使用:MemDC.CreateCompatibleDC(m_pDC); / 创建内存兼容设备上
4、下文MemBitmap.CreateCompatibleBitmap(m_pDC,xRange,yRange); / 创建内存兼容画布,大小由逻辑范围决定pOldbitmap = MemDC.SelectObject(&MemBitmap); / 将画布选入内存设备上下文MemDC.FillSolidRect(0,0,xRange,yRange,RGB(123,213,132); / 对内存中的画布填充背景颜色,否则是默认的黑色/ 画图操作,如画一条对角直线MemDC.MoveTo(0,0);MemDC.LineTo(xRange*0.9,yRange*0.9);/ 将内存中的画图区域拷贝到界
5、面的控件区域上去/ 第1和第2个参数若是0时,则从物理坐标的(rect.left,rect.bottom)点上开始按上述指定的方向贴图m_pDC-BitBlt(0,0,xRange,yRange,&MemDC,0,0,SRCCOPY);5. 在类的析构函数中:MemDC.SelectObject(pOldbitmap);bitmap.DeleteObject();this-ReleaseDC(m_pDC);6. 至此,就完成了双缓冲及坐标缩放绘图的功能*用VC做的画图程序,当所画的图形大于屏幕时,在拖动滚动条时屏幕就会出现严重的闪烁,为了解决这一问题,就得使用双缓冲来解决。程序产生严重的闪烁问
6、题是因为画图过程中前后两次的画面反差很大造成的人的视觉的闪烁。因为在VC中每次在调用OnDraw时系统都是先用背景画刷将画布清除再执行画图命令,这样在你每次移动滚动条时每执行一次OnDraw就会有一个空白页,这样和你的最终结果图象之间有一个很大的反差,因而看起来闪烁,而且滚动条滚动越快闪烁越严重。当然,你可以将背景画刷设为NULL,这样可以解决闪烁问题,但是不能将先前的图象擦除,这样整个屏幕就显得很乱。下面将利用双缓冲来解决这一问题的思路给大家作一下简单的介绍。我先来解释一下在MFC里面很关键的设备环境描述符,也就是所谓的 DC(device context)。在dos时代,我们如果要绘图,必
7、须通过一系列系统函数来启动图形环境(用过turbo pascal或者turbo c的人该还有印象吧),这之间对各种硬件的初始化参数都不相同,非常的烦人,常常还要查阅硬件手册,那时的程序智能针对最流行的硬件来编写,对不流行的就没有办法了。windows操作系统为了屏蔽不同的硬件环境,让编程时候不考虑具体的硬件差别,采取了一系列办法,设备环境描述符就是这样产生的。简单地说,设备描述符抽象了不同的硬件环境为标准环境,用户编写时使用的是这个虚拟的标准环境,而不是真实的硬件,与真实硬件打交道的工作一般交给系统和驱动程序去完成(这同样解释了为什么我们需要经常更新驱动程序的问题)。使用在windows图形系
8、统(gdi,而不包括direct x)上面,就体现在一系列的图形DC上面,我们如果要在gdi上面绘图,就必须先得到图形DC的句柄(handle),然后在指定句柄的基础上进行图形操作。那么我们怎么在sdk环境下面绘图的呢,我想这个大家都不太清楚,但是确实很基础。在windows的sdk环境下面,我们用传统的c编写程序,在需要的绘图地方(比如响应WM_PAINT消息的分支)这样做:hdc = GetDC( hwnd );oldGdiObject = SelectObject( hdc,newGdiObject );.绘图操作.SelectObject( hdc,oldGdiObject );Del
9、eteObject( newGdiObject );ReleaseDC( hdc);或者这样BeginPaint( hwnd,&ps ); /PAINTSTRUCT ps - ps is a paint struct .绘图操作.EndPaint( hwnd )这就是大概的过程,我们看到了hdc(图形DC句柄)的应用。在绘图的部分,每一个绘图函数基本上也要用到这个句柄,最后我们还必须释放它,否则将严重影响性能。每次我们都必须调用GetDC这个api函数得到(不能用全局变量保存结果重复使用,我在后面将做解释)。这些是最最基本的windows图形操作的方式,相比dos时代简单了些,但是有些概念也难
10、理解了些。vb里面的简单的point函数其实最后也是被转化为这样的方式来执行,系统帮助做了很多事情。到了MFC里面,由于有了封装,所有的hdc被隐藏在对象中做为隐藏参数来传递(就是DC类的this啦),所以我们的关键话题就转变为了怎样得到想要的DC类而已。这个过程其实大同小异。在消息响应的过程中,WM_PAINT被转变为OnDraw()或是OnPaint()之类的一系列函数来响应,这些函数一般都有个参数CDC *pDC传入进来,因此在这些函数里面,我们只需直接画图就可以了,和以前sdk的方式一样。但是WM_PAINT消息响应的频度太高了,比如最小化最大化,移动窗体,覆盖等等都引起重绘,经常的这
11、样画图,很是消耗性能;在有些场合,比如随机作图的场合,每一次就改变,还导致了程序的无法实现。怎么解决后一种问题呢。ms在msdn的例子里面交给我们document/view的经典解决办法,将图形的数据存储在document类里面,view类只是根据这些数据绘图。比如你要画个圆,只是将圆心和半径存在document里面,view类根据这个里面的数据在屏幕上面重新绘制。那么,我们只需要随机产生一次数据就可以了。这样还是存在性能的问题,于是我们开始考虑另外的解决方法。我们知道,将内存中的图片原样输出到屏幕是很快的,这也是我们在dos时代经常做的事情,能不能在windows也重新利用呢?答案就是内存缓
12、冲绘图。这就是我们今天的主题。我们还是回到DC上来,既然DC是绘图对象,我们也就可以自己在内存里面造一个,让它等于我们想要的绘图对象,图(CBitmap)可以存储在document类里面,每一次刷新屏幕都只需将这个图输出到屏幕上面,每一次作图都是在内存里面绘制,保存在document的图里面,必要时还可以将图输出到外存保存。这样既保证了速度,也解决了随机的问题,在复杂作图的情况下对内存的开销也不大(总是一副图片的大小)。这是一个很好的解决办法,现在让我们来实现它们。1. 我们首先在document类里面保存一个图片CBitmap m_bmpBuf; /这里面保存了我们做的图,存在于内存中2.
13、其次在view类里面,我们需要将这个图拷贝到屏幕上去,于OnDraw(CDC *pDC)函数中:CDC dcMem; / 以下是输出位图的标准操作CBitmap *pOldBitmap = NULL;dcMem.CreateCompatibleDC(NULL);pOldBitmap = dcMem.SelectObject(&pDoc-m_bmpBuf);BITMAP bmpinfo;pDoc-m_bmpBuf.GetBitmap(&bmpinfo);pDC-BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);dcMe
14、m.SelectObject(pOldBitmap);dcMem.DeleteDC();3. 在我们需要画图的函数里,完成绘图工作CBmpDrawDoc *pDoc = GetDocument(); / 得到document中的bitmap对象 CDC *pDC = GetDC();CDC dcMem;dcMem.CreateCompatibleDC(NULL); / 这里我们就在内存中虚拟建造了DCpDoc-m_bmpBuf.DeleteObject();pDoc-m_bmpBuf.CreateCompatibleBitmap(pDC,100,100); / 依附DC创建bitmapCBit
15、map *pOldBitmap = dcMem.SelectObject(&pDoc-m_bmpBuf); / 调入了我们的bitmap目标dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255); / 这些是绘图操作,随便你_dcMem.TextOut(0,0,Hello,world!);dcMem.Rectangle(20,20,40,40);dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0);pDC-BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY); / 拷贝到屏幕dcMem.Sel
16、ectObject(pOldBitmap);dcMem.DeleteDC();全部的过程就是这样,很简单吧。以此为例子还可以实现2个缓冲或者多个缓冲等等,视具体情况而定。当然在缓冲区还可以实现很多高级的图形操作,比如透明,合成等等,取决于具体的算法,需要对内存直接操作(其实就是当年dos怎么做,现在还怎么做)。再来解释一下前面说的为什么不能用全局变量保存DC问题:其实DC也是用句柄来标识的,所以也具有句柄的不确定性,就是只能随用随取,不同时间两次取得的是不同的(使用过文件句柄地话,应该很容易理解的)。那么我们用全局变量保存的DC就没什么意义了,下次使用只是什么也画不出来。(这一点的理解可以这样
17、:DC需要占用一定的内存,那么在频繁的页面调度中,位置难免改变,于是用来标志指针的句柄也就不同了)。* 显示图形如何避免闪烁 显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。而且多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。 我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈我的一些观点。 1、显示的图形为什么会闪烁? 我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进
18、行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。 当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。 有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。 例如在OnDraw(CDC *pDC)中这样写: pDC-MoveTo(0,0); pDC-LineTo(100,
19、100); 这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画只会闪烁一次。这个也可以试验,在OnDraw(CDC *pDC)中这样写: for(int i=0;iMoveTo(0,i); pDC-LineTo(1000,i); 呵呵,程序有点变态,但是能说明问题。 说到这里可能又有人要说了,为
20、什么一个简单图形看起来没有复杂图形那么闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要闪得厉害一些,但是闪烁频率要低。那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,不闪才怪呢。 2、如何避免闪烁 在知道图形显示闪烁的原因之后,对症下药就好办了。 (1). 首先是去掉MFC 提供的背景绘制过程。实现的方法很多: * 可以在窗口形成时给窗口的注册类的背景刷赋NULL * 也可以在形成以后修改背景 static C
21、Brush brush(RGB(255,0,0); SetClassLong(this-m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush); * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE 这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,变得一团乱。怎么办? (2). 这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)
22、。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。 3、如何实现双缓冲 首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中: CRect rc; / 定义一个矩形区域变量GetClientRect(rc); int nWidth = rc.Width(); int nHeight = rc.Height(); CDC *pDC = GetDC(); / 定义设备上下文CDC MemDC; / 定义一个内存显示设备对象 CBitmap
23、MemBitmap; / 定义一个位图对象/建立与屏幕显示兼容的内存显示设备 MemDC.CreateCompatibleDC(pDC);/建立一个与屏幕显示兼容的位图,位图的大小可选用窗口客户区的大小 MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);/将位图选入到内存显示设备中,只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);/先用背景色将位图清除干净,否则是黑色。这里用的是白色作为背景 MemDC.FillSolidRect
24、(0,0,nWidth,nHeight,RGB(255,255,255); /绘图操作等在这里实现MemDC.MoveTo(); MemDC.LineTo(); MemDC.Ellipse();/将内存中的图拷贝到屏幕上进行显示 pDC-BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY); /绘图完成后的清理MemDC.SelectObject(pOldbitmap); MemBitmap.DeleteObject();上面的注释应该很详尽了,废话就不多说了。 4、如何提高绘图的效率 我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千
25、上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。 实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不
26、会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC-GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。但如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高
27、。*双缓存即现在内存dc中作图,而后一次性地拷贝到屏幕上,所以提高了绘图的速度。但只用此方法不能根本解决闪烁的问题。 而将响应 WM_ERASEBKGND 的重载函数 OnEraseBkgnd(CDC* pDC) 直接返回TRUE是最好的办法。 如下: BOOL CMyWin:OnEraseBkgnd(CDC* pDC) return TRUE; /return CWnd:OnEraseBkgnd(pDC); /把系统原来的这条语句注释掉*如何修改控件的背景模式及控件的字体颜色1. 改变对话框的背景色在CApp类中的InitInstance()里添加 SetDialogBkColor(RGB(
28、0,192,0),RGB(0,0,0);2. 如果想改变静态文本或单选按钮的背景色,首先需要获得控件ID,然后设置背景色,具体步骤:(1) 响应对话框类的WM_CTLCOLOR消息,生成OnCtlColor函数(2) 为对话框类添加成员变量CBrush m_brush; 并在初始化函数中初始化m_brush.CreateSolidBrush(RGB(0,255,0); /颜色在这里设置(3) 在OnCtlColor函数中添加代码,以改变控件的文字颜色和背景色switch(pWnd-GetDlgCtrlID()case(IDC_INPUT):pDC-SetTextColor(RGB(255,0,192);pDC-SetBkMode(TRANSPARENT);return m_brush;break;case(IDC
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1