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

时间: 08-24

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

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

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

大数据量下的高并发分布式访问控制(ACL)优化方案(一)

目前的设计方案 1.1.控制计数: 在目前的项目中,有很多接口需要对访问方进行权限访问控制.目前设计方案是:利用redis集群来存储每个访问控制点的访问计数信息.Key值为=PlatformId(接入平台方)+InterfaceId(系统接口)+dayTime(日期时间),value值为当天每个时段的访问次数统计列表. 1.2.控制规则: 通过页面配置并制定控制规则.业务系统在启动时加载控制规则,并访问redis获取控制次数,然后在业务系统中做逻辑判断完成,ACL控制做请求拦截处理. 目前的痛点

大数据量下高并发同步的讲解(转)

文章转自:http://blog.csdn.net/xcw931924821/article/details/52475742 *************************************************************************************************************************************************************************************** 对于

WCF大数据量传输解决方案

文章内容列表:1. 场景:2. 解决方案3. WCF契约与服务实现设计静态图4. WCF契约与服务实现设计详细说明6. 服务端启动服务代码:7. 客户端代码8.   WCF大数据量传输解决方案源码下载 1. 场景: WCF在网络传输中,大数据量传输造成网络阻塞,宽带无法承受: 2. 解决方案 解决WCF在网络传输中的大数据量问题: A.需要把相关数据序列化成字节流,再对字节流进行压缩,再进行传输,到了客户端再做反向操作便可获得原始数据. B.如果压缩后的数据仍然较大时,可以再压缩流后,再对流进行

解决WCF大数据量传输 ,System.Net.Sockets.SocketException: 远程主机强迫关闭了一个现有的连接

开发中所用的数据需要通过WCF进行数据传输,结果就遇到了WCF大量传输问题 也就是提示System.Net.Sockets.SocketException: 远程主机强迫关闭了一个现有的连接 网上解决方案都是千篇一律互相转发的,并且没有明确的解决方案或者按照,各个博客中的解决方案都没能解决这个问题. 为此我整整浪费了一天时间用来解决这个问题,而且用了最笨的办法一点点的尝试网上所查到的方案.对于精研WCF来说的这可能是一个小问题,但是对于仅仅了解wcf,一知半解的会很困惑.将解决方案贴出来希望能帮

MySQL随机获取数据的方法,支持大数据量

最近做项目,需要做一个从mysql数据库中随机取几条数据出来. 总所周知,order by rand 会死人的..因为本人对大数据量方面的只是了解的很少,无解,去找百度老师..搜索结果千篇一律.特发到这里来,供大家学习. 在mysql中带了随机取数据的函数,在mysql中我们会有rand()函数,很多朋友都会直接使用,如果几百条数据肯定没事,如果几万或百万时你会发现,直接使用是错误的.下面我来介绍随机取数据一些优化方法. SELECT * FROM table_name ORDER BY ran

大数据量高并发的数据库优化

一.数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能.所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的. 在一个系统分析.设计阶段,因为数据量较小,负荷较低.我们往往只注意到功能的实现,而很难注意到性能的薄弱之处,等到系统投入实际运行一段时间后,才发现系统的性能在降低,这时再来考虑提高系统性能则要花费更多的人力物力,而整个系统也不可避免的形成了一个打补丁工程. 所以在考虑整个系统的流程的时候,我们必须

mysql innobackupex xtrabackup 大数据量 备份 还原(转)

原文:http://blog.51yip.com/mysql/1650.html 作者:海底苍鹰 大数据量备份与还原,始终是个难点.当MYSQL超10G,用mysqldump来导出就比较慢了.在这里推荐xtrabackup,这个工具比mysqldump要快很多. 一.Xtrabackup介绍 1,Xtrabackup是什么 Xtrabackup是一个对InnoDB做数据备份的工具,支持在线热备份(备份时不影响数据读写),是商业备份工具InnoDB Hotbackup的一个很好的替代品. Xtra

大并发大数据量请求的处理方法

大并发大数据量请求一般会分为几种情况: 1.大量的用户同时对系统的不同功能页面进行查找,更新操作 2.大量的用户同时对系统的同一个页面,同一个表的大数据量进行查询操作 3.大量的用户同时对系统的同一个页面,同一个表进行更新操作 对于第一种情况一般处理方法如下: 一.对服务器层面的处理 1. 调整IIS 7应用程序池队列长度 由原来的默认1000改为65535. IIS Manager > ApplicationPools > Advanced Settings Queue Length : 6

大数据量下的SQL Server数据库自身优化 (转载)

1.1:增加次数据文件 从SQL SERVER 2005开始,数据库不默认生成NDF数据文件,一般情况下有一个主数据文件(MDF)就够了,但是有些大型的数据库,由于信息很多,而且查询频繁,所以为了提高查询速度,可以把一些表或者一些表中的部分记录分开存储在不同的数据文件里 由于CPU和内存的速度远大于硬盘的读写速度,所以可以把不同的数据文件放在不同的物理硬盘里,这样执行查询的时候,就可以让多个硬盘同时进行查询,以充分利用CPU和内存的性能,提高查询速度. 在这里详细介绍一下其写入的原理,数据文件(