VC++大数据量绘图时无闪烁刷屏技术实现(我的理解是,在内存上作画,然后手动显示,而不再直接需要经过WM_PAINT来处理了)

http://hantayi.blog.51cto.com/1100843/383578

引言

  当我们需要在用户区显示一些图形时,先把图形在客户区画上,虽然已经画好但此时我们还无法看到,还要通过 程序主动地刷新用户区,强制Windows发送一条WM_PAINT消息,这将引发视类OnDraw函数简单地将所有的图形对象重画,这样才完成了图形的 显示工作,但在刷新的同时会引起较明显的闪烁尤其是当画面面积较大、图像元素过多时尤为明显甚至达到无法正常工作的地步。因此,我们需要做相应的处理。本 文介绍了采用先在内存中绘制图形,然后再把绘好的图形以位图方式从内存拷贝到窗口客户的消除刷屏闪烁的一种方法。

WM_PAINT消息和无效区

·在用户移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见。

·用户改变窗口的大小。

·滚动窗口用户区。

·程序调用InvalidateRect或InvalidateRgn函数显式地发送一条WM_PAINT消息。

当上面情况之一发生时,就要求应用程序一定刷新其用户区的一部分或全部,Windows会向窗口函数发送一条WM_PAINT消息。另外,当 Windows删除覆盖窗口部分区域的对话框或消息框时和菜单下拉出来又被释放时窗口用户区被临时覆盖,系统会试图保存显示区域,但是不一定能成功,可能 向窗口函数发送一条WM_PAINT消息,要求应用程序刷新其用户区。需要说明的是:光标或图符穿过窗口用户区时,也可能覆盖显示内容,但这种情况下,系 统一定能保留并恢复被覆盖的区域,所以此时并不会发送WM_PAINT消息来要求应用程序去刷新其显示区。在Windows  应用程序的窗口函数中,对WM_PAINT消息的处理就是刷新其用户区,这是一种固定的程序结构。

为提高刷新效率,我们可以只刷新用户区的一小部分,其余没有发生变化的我们可以不予刷新,窗口函数可以通过调用函数InvalidateRect显式 地使用户区内的一个矩形无效。而且只有当窗口客户区的某一部分失效时,其窗口函数才会收到WM_PAINT消息。

刷屏闪烁的产生原因与解决方法

当客户区有所改动,而又要将改动显示出来,就必然要强制Windows发送一条WM_PAINT消息,从而引发OnDraw函数的重画,这样虽完成了 图形的显示,却也会引起较明显的闪烁,当画面上数据不是很多时尚不明显,当客户区有成千上万个点的时候刷新一次会引起整幅画面的剧烈跳动,尤其是对于许多 实时监控软件和矢量电子地图软件,此类软件通常在屏幕上都会动辄几千、几万个要素点,很明显单靠发送WM_PAINT 消息引发OnDraw  的重画根本满足不了实际需求。

为了解决上述问题,我们需要做一些相应的处理。首先要先检取无效区,然后创建一个与原设备环境句柄pDC相兼容的内存设备环境,之后就可以采用在内存 中绘制图形并把绘好的图形以位图方式从内存拷贝到窗口客户的方法来消除刷屏时引起的闪烁。这还需要创建一个与原设备环境句柄pDC相兼容的、大小为整个客 户区的位图。然后再使新的设备环境dc与pDC具有同样的映射关系,将位图选入内存环境。再使dc的整个客户区都成无效区,再“与上”所检取的无效区,使 内存环境与pDC检取的无效区相等。之后便可以进行绘图工作了,绘图完毕之后应当释放所获取的设备环境句柄pDC。否则会造成系统资源的浪费。

程序示例

本示例程序通过打开任意存档文件,将其ASCII码码值当作要显示的数据,并通过一图画控件将其数据以图形的形式依次显示出来。本程序要处理的数据量较大,如不采用本文所述方法将会有很明显的闪烁。
首先新建一基于CFormView的单文档应用程序WaveShower并在Form上添加一"picture"控件,设置其ID为 IDC_SCREEN、Type为Rectangle、Color为Black。在"Extended Styles"属性页里选中Modal  Frame检查框。继续添加一菜单“打开数据文件”,并生成其响应函数OnOpenData()。同时在视类中添加如下成员变量:

int m_BufLen; //数据长度
unsigned char* buffer; //数据缓存
int m_dx; //数据偏移量 
int m_DY; //数据显示区的幅度
CPoint* value; //将要显示的数值
int m_DX; //数据显示区的宽度
int m_Y0; //数据显示区参照点位置
CRect rect; //数据显示区矩形

然后在视类中添加函数GetScreenRect()用以获取数据显示区的大小及其他参数;添加函数CleanScreen()完成清除数据显示区的功能;添加函数DrawPoint()以便在数据显示区画点:

void CWaveShowerView::GetScreenRect()
            {
            CWnd* pStatic = GetDlgItem(IDC_SCREEN);
            pStatic->GetWindowRect(&rect);
            ScreenToClient(&rect);
            rect.top+=4;
            rect.left+=4;
            rect.bottom-=4;
            rect.right-=4;
            m_Y0=(rect.bottom-rect.top)/2+rect.top;
            m_DX=rect.Width();
            m_DY=rect.Height()/2;
            value=new CPoint[m_DX];
            }
            void CWaveShowerView::CleanScreen()
            {
            CDC* pDC=GetDC();
            CPen pen1(PS_SOLID,1,RGB(0,0,0));
            CPen* oldPen1=pDC->SelectObject(&pen1);
            for(int i=rect.top;i<rect.bottom;i++)
            {
            pDC->MoveTo(rect.left,i);
            pDC->LineTo(rect.right,i);
            }
            pDC->SelectObject(&oldPen1);
            CPen pen2(PS_SOLID,1,RGB(0,0,255));
            CPen* oldPen2=pDC->SelectObject(&pen2);
            pDC->MoveTo(rect.left,m_Y0);
            pDC->LineTo(rect.right,m_Y0);
            pDC->SelectObject(&oldPen2);
            ReleaseDC(pDC);
            }
            void CWaveShowerView::DrawPoint(CPoint pt, COLORREF color)
            {
            CDC* pDC=GetDC();
            pDC->SetPixel(rect.left+pt.x,m_Y0-pt.y,color);
            ReleaseDC(pDC);
            }

接下来,在视类的OnInitialUpdate()初始化函数中添加代码以进行数据显示的各项前期准备工作,并在“打开数据文件”菜单的响应函数中添加代码以读取文件的内码。

void CWaveShowerView::OnInitialUpdate()
            {
            CFormView::OnInitialUpdate();
            GetParentFrame()->RecalcLayout();
            ResizeParentToFit();
            GetScreenRect();
            for(int i=0;i<m_DX;i++)
            value[i].x=value[i].y=0;
            SetTimer(0,10,NULL);
            }
            void CWaveShowerView::OnOpenData() 
            {
            CString FileName="";
            CFile file;
            CFileDialog dlg(TRUE,"*","*.*",
            OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,"所有文件(*.*)|*.*||",NULL);
            if(dlg.DoModal()==IDOK)
            {
            KillTimer(1);
            FileName=dlg.GetPathName();
            file.Open(FileName,CFile::modeReadWrite);
            m_BufLen=file.GetLength();
            buffer= new unsigned char[m_BufLen+m_DX+10];
            file.Read(buffer,m_BufLen);
            file.Close();
            SetTimer(1,10,NULL);
            }
            }

  下面将要添加的定时器响应函数正是本文的重点,为方便对比起见,笔者写了两个OnTimer响应函数,前一个是采用常 规的普通方法描点的,运行起来可以很明显地看到画面的闪烁跳动。而后一种则是采用本文所述方法采用的内存画图的方法,运行后几乎画面无闪烁。下面便是两段 对比代码的原码部分:

//代码一:有闪烁的代码
void CWaveShowerView::OnTimer(UINT nIDEvent) 
            {
            if(nIDEvent==0)
            {
            CleanScreen();
            for(int i=0;i<m_DX;i++)
            DrawPoint(value[i],RGB(0,255,0));
            }
            if(nIDEvent==1)
            {
            m_dx+=2;
            for(int i=0;i<m_DX;i++)
            {
            value[i].x=i;
            if(m_dx+i<0)
            buffer[m_dx+i]=128;
            if(m_dx+i<-m_DX)
            m_dx-=2;
            if(m_dx+i>m_BufLen)
            buffer[m_dx+i]=128;
            if(m_dx+i>m_BufLen+m_DX)
            m_dx-=2;
            value[i].y=m_DY*(buffer[m_dx+i]-128)/256;
            }
            }
            CFormView::OnTimer(nIDEvent);
            }
            //代码二:无闪烁的代码
void CWaveShowerView::OnTimer(UINT nIDEvent) 
            {
            if(nIDEvent==0)
            {
            CDC* pDC=GetDC();
            CDC dc; 
            CBitmap bitmap; 
            CBitmap* pOldBitmap; 
            CRect client; 
            pDC->GetClipBox(client); //检取无效区 
//创建一个与pDC兼容的内存设备环境 
if(dc.CreateCompatibleDC(pDC)) 
            { 
            //创建一与pDC兼容的位图,大小为整个客户区 
if(bitmap.CreateCompatibleBitmap(pDC,rect.Width(), rect.Height()))
            { 
            //使dc与pDC具有同样的映射关系 
OnPrepareDC(&dc,NULL); 
            //将位图选入内存环境
pOldBitmap=dc.SelectObject(&bitmap); 
            //使dc的整个客户区都成无效区 
dc.SelectClipRgn(NULL); 
            //再“与上”检取的无效区,使内存环境与
//pDC检取的无效区相等 
dc.IntersectClipRect(client); 
            } 
            } 
            CleanScreen();
            for(int i=0;i<m_DX;i++)
            DrawPoint(value[i],RGB(0,255,0));
            dc.SelectObject(pOldBitmap); 
            ReleaseDC(pDC); 
            }
            if(nIDEvent==1)
            {
            m_dx+=2;
            for(int i=0;i<m_DX;i++)
            {
            value[i].x=i;
            if(m_dx+i<0)
            buffer[m_dx+i]=128;
            if(m_dx+i<-m_DX)
            m_dx-=2;
            if(m_dx+i>m_BufLen)
            buffer[m_dx+i]=128;
            if(m_dx+i>m_BufLen+m_DX)
            m_dx-=2;
            value[i].y=m_DY*(buffer[m_dx+i]-128)/256;
            }
            }
            CFormView::OnTimer(nIDEvent);
            }

虽然通过上述几步可以实现所有的功能,但为了防止内存泄露和养成良好的编程习惯,我们还须做些工作,在视类的构造函数中释放我们曾经申请过的内存以及定时器:

CWaveShowerView::~CWaveShowerView()
            {
            delete[] value;
            KillTimer(0);
            KillTimer(1);
            }

小结

编译运行此程序,通过菜单选取需要显示的文件(任意文件均可),如在定时器响应代码中采用的是第一种代码,则会看到数据显示的同时伴随着明显的闪烁而 采用后一种代码编码则会很平稳的将数据显示出来。本文介绍的这种方法适用于各种牵扯到数组数据图形显示的程序,比如监控软件、数据分析软件、测量软件等 等,具有广泛的应用前景。本文所述程度代码在Windows 2000 Professional + SP4下由Microsoft Visual  C++ 6.0编译通过。

vc双缓冲:VC++双缓冲实现方法 (简单的较好的)

http://hantayi.blog.51cto.com/1100843/383579

在图形图象处理编程过程中,双缓冲是一种基本的技术。我们知道,如果窗体在响应WM_PAINT消息
的时候要进行复杂的图形处理,那么窗体在重绘时由于过频的刷新而引起闪烁现象。解决这一问题的有效方法
就是双缓冲技术。

因为窗体在刷新时,总要有一个擦除原来图象的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,然
后在调用新的绘图代码进行重绘,这样一擦一写造成了图象颜色的反差。当WM_PAINT的响应很频繁的时候
,这种反差也就越发明显。于是我们就看到了闪烁现象。

我们会很自然的想到,避免背景色的填充是最直接的办法。但是那样的话,窗体上会变的一团糟。因为每次绘
制图象的时候都没有将原来的图象清除,造成了图象的残留,于是窗体重绘时,画面往往会变的乱七八糟。所
以单纯的禁止背景重绘是不够的。我们还要进行重新绘图,但要求速度很快,于是我们想到了使用BitBlt函数。
它可以支持图形块的复制,速度很快。我们可以先在内存中作图,然后用此函数将做好的图复制到前台,同时
禁止背景刷新,这样就消除了闪烁。以上也就是双缓冲绘图的基本的思路。

一、普通方法:

先按普通做图的方法进行编程。即在视类的OnDraw函数中添加绘图代码。在此我们绘制若干同心圆,代
码如下:

CBCDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CPoint ptCenter;

CRect rect,ellipseRect;

GetClientRect(&rect);

ptCenter = rect.CenterPoint();

for(int i=20;i>0;i--)

{

ellipseRect.SetRect(ptCenter,ptCenter);

ellipseRect.InflateRect(i*10,i*10);

pDC->Ellipse(ellipseRect);

}

编译运行程序,尝试改变窗口大小,可以发现闪烁现象。

二、双缓冲方法:

在双缓冲方法中,首先要做的是屏蔽背景刷新。背景刷新其实是在响应WM_ERASEBKGND消息。我们在
视类中添加对这个消息的响应,可以看到缺省的代码如下:

BOOL CMYView::OnEraseBkgnd(CDC* pDC)

{

return CView::OnEraseBkgnd(pDC);

}

是调用父类的OnEraseBkgnd函数,我们屏蔽此调用,只须直接return TRUE;即可。

下面是内存缓冲作图的步骤。

CPoint ptCenter;

CRect rect,ellipseRect

GetClientRect(&rect);

ptCenter = rect.CenterPoint();

CDC dcMem; //用于缓冲作图的内存DC

CBitmap bmp; //内存中承载临时图象的位图

dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC

bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//创建兼容位图

dcMem.SelectObject(&bmp); //将位图选择进内存DC  

//按原来背景填充客户区,不然会是黑色  

dcMem.FillSolidRect(rect,pDC->GetBkColor()); 

for(int i=20;i>0;i--) //在内存DC上做同样的同心圆图象

{

ellipseRect.SetRect(ptCenter,ptCenter);

ellipseRect.InflateRect(i*10,i*10);

dcMem.Ellipse(ellipseRect);

}

pDC->BitBlt(0,0,rect.Width(),rect.Height(),  

& dcMem,0,0,SRCCOPY);//将内存DC上的图象拷贝到前台  

dcMem.DeleteDC(); //删除DC

bm.DeleteObject(); //删除位图

由于复杂的画图操作转入后台,我们看到的是速度很快的复制操作,自然也就消除了闪烁现象。

注意:bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());

这里面CreateCompatibleBitmap第一个参数不能用dcMem,这样的话创建的是黑白位图。如果你要创建彩色
位图,需要用pDC,它用来创建了内存DC. 详细请见下面的MSDN:

When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into
it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a
monochrome bitmap. To create a color bitmap, use the hDC that was used to create the memory
device context, as shown in the following code:

HDC memDC = CreateCompatibleDC ( hDC );

HBITMAP memBM = CreateCompatibleBitmap ( hDC, nWidth, nHeight );

SelectObject ( memDC, memBM );            2008-11-6 16:45:41

http://blog.csdn.net/jiangxinyu/article/details/8064620

时间: 2024-10-13 02:46:27

VC++大数据量绘图时无闪烁刷屏技术实现(我的理解是,在内存上作画,然后手动显示,而不再直接需要经过WM_PAINT来处理了)的相关文章

大数据量传输时配置WCF的注意事项

原文:大数据量传输时配置WCF的注意事项 WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMessageSize:获取或设置配置了此绑定的通道上可以接收的消息的最大大小. basicHttpBinding等预定义的绑定一般具有MaxReceivedMessageSize属性,CustomBinding则需要在Transport中定义. 示例代码: <bindings> <custom

关于webservice大数据量传输时的压缩和解压缩

当访问WebSerivice时,如果数据量很大,传输数据时就会很慢.为了提高速度,我们就会想到对数据进行压缩.首先我们来分析一下. 当在webserice中传输数据时,一般都采用Dataset进行数据传输.执行的过程就是先把Dataset转化为xml进行传输,Dataset转化为xml的格式如下: [html] view plaincopy <DataSetName> <DataTableName> <Column1Name>.......</Column1Nam

hadoop job解决大数据量关联时数据倾斜的一种办法

转自:http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2161929.html http://www.geminikwok.com/2011/04/02/hadoop-job解å?³å¤§æ?°æ?®é??å?³è??æ—¶æ?°æ?®å?¾æ??ç??ä¸?ç§?å??æ³?/ 数据倾斜是指,map /reduce程序执行时,reduce节点大部分执行完毕,但是有一个或者几个reduce节点运行很慢,导致整个程序的处理时间很长,这是因为

java处理大数据量任务时的可用思路--未验证版,具体实现方法有待实践

1.Bloom filter 适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集 基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数.将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的.同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字.所以一个简单的改进就是 counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了.

使用netty4.x客户端接收较大数据量报文时发生的读取不完整bug修复记录

1.先说问题 背景:服务是运行在Linux上的安全网关提供的,TCP协议发送 通过二进制编码的xml字符串 报文,报文头的第一个字段是int类型的表示字节序标记,第二个字段是int类型的表示整个报文长度. 现象:数据量较小时完全可以正常收发报文,当服务端发送的报文数据量较大时(本例是将近600k)概率性出现接收数据直接调用readComplete()方法而没有走channelRead() 跟踪:跟踪代码发现出问题时context 的 read() 方法执行中读取到一百多k(有时两百多也可能三百多

使用内存映射文件MMF实现大数据量导出时的内存优化

前言 导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现 我们以单次导出一个excel举例(csv同理),excel包含1~n个sheet,在每个sheet中存储的按行和列的坐标在单元格存储具体数据,如果我们要使用MMF,第一个要考虑的就是如何将整个excel合理的存储到MMF中.这里我们引入MMF两个对象: MemoryMappedFile --表示内存映射文件 MemoryMappedVie

tomcat优化---大数据量提交tomcat时,tomcat无法接收导致页面无反应

关于tomcat的一个优化问题: 有时候保存大数据量的数据时.tomcat不优化的话,页面会没反应.tomcat后台并不报错,仅仅是提示以下内容: 警告: More than the maximum number of request parameters (GET plus POST) for a s ingle request ([10,000]) were detected. Any parameters beyond this limit have be en ignored. To c

大数据量时Mysql的优化要点

如今随着互联网的发展,数据的量级也是撑指数的增长,从GB到TB到PB.对数据的各种操作也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求.这个时候NoSQL的出现暂时解决了这一危机.它通过降低数据的安全性,减少对事务的支持,减少对复杂查询的支持,来获取性能上的提升.但是,在有些场合NoSQL一些折衷是无法满足使用场景的,就比如有些使用场景是绝对要有事务与安全指标的.这个时候NoSQL肯定是无法满足的,所以还是需要使用关系性数据库. 虽然关系型数据库在海量数据中逊色于NoSQL

大数据量时Mysql的优化

(转自网络) 如今随着互联网的发展,数据的量级也是撑指数的增长,从GB到TB到PB.对数据的各种操作也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求.这个时候NoSQL的出现暂时解决了这一危机.它通过降低数据的安全性,减少对事务的支持,减少对复杂查询的支持,来获取性能上的提升.但是,在有些场合NoSQL一些折衷是无法满足使用场景的,就比如有些使用场景是绝对要有事务与安全指标的.这个时候NoSQL肯定是无法满足的,所以还是需要使用关系性数据库. 虽然关系型数据库在海量数据中逊