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

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

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

    原文:大数据量传输时配置WCF的注意事项 WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMe ...

  2. 【转载】大数据量传输时配置WCF的注意事项

    WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMessageSize:获取或设置配置了此绑定 ...

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

    转自:http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2161929.html http://www.geminikwok.com/2011/04 ...

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

    1.Bloom filter适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数.将hash函数对应的值的位数组置1,查找时如 ...

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

    1.先说问题 背景:服务是运行在Linux上的安全网关提供的,TCP协议发送 通过二进制编码的xml字符串 报文,报文头的第一个字段是int类型的表示字节序标记,第二个字段是int类型的表示整个报文长 ...

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

    当访问WebSerivice时,如果数据量很大,传输数据时就会很慢.为了提高速度,我们就会想到对数据进行压缩.首先我们来分析一下. 当在webserice中传输数据时,一般都采用Dataset进行数据 ...

  7. c# 大数据量比较时-方案

    1.当面临千万条数据量的比较时,从技术的角度来说应该用泛型键值(c#键值由于用了散列算法速度很快).例如前几天我需要查的是 航空公司.出发.到达.返点可以将 航空公司-出发-到达做一个键,返点作为值. ...

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

    前言 导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现 我们以单次导出一个excel举例(csv ...

  9. VC无闪烁刷屏技术的实现【转】

    转自:http://blog.csdn.net/scorpio_tiger/article/details/2888719 http://www.pconline.com.cn/pcedu/empol ...

随机推荐

  1. 进阶篇,第二章:MC与Forge的Event系统

    <基于1.8 Forge的Minecraft mod制作经验分享> 这一章其实才应该是第一章,矿物生成里面用到了Event的一些内容.如果你对之前矿物生成那一章的将算法插入ORE_GEN_ ...

  2. PHPMailer中文说明

    PHPMailer中文说明 A开头: $AltBody --属性出自:PHPMailer ::$AltBody文件:class.phpmailer .php说明:该属性的设置是在邮件正文不支持HTML ...

  3. python之Lambda函数---笔记

    <Python3 程序开发指南> Lambda函数,是一个匿名函数,创建语法: lambda parameters:express parameters:可选,如果提供,通常是逗号分隔的变 ...

  4. Pyhton开发【第五篇】:Python基础之杂货铺

    Python开发[第五篇]:Python基础之杂货铺   字符串格式化 Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进 ...

  5. Android 静默安装/后台安装

    Android实现静默安装其实很简单,今天在网上找资料找半天都说的很复杂,什么需要系统安装权限.调用系统隐藏的api.需要系统环境下编译.需要跟systemUI同进程什么的.我不知道他们真的实现了静默 ...

  6. C#解leetcode 11. Container With Most Water

    Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai).  ...

  7. composer之安装

    最近想要学习下yii框架,所以,就看了下官网,看到了貌似比较依赖composer这个东西,然后我就安装了,但是会有问题,安装不上等等问题,不论是windows还是linux命令行安装,都是因为一个问题 ...

  8. Linq101-Join

    using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { internal clas ...

  9. ExcelApplication 另存Excel的SaveAs函数

    procedure SaveAs(const Filename: WideString; FileFormat: OleVariant; Password: OleVariant; WriteResP ...

  10. Linux下解决高并发socket最大连接数所受的各种限制(解除IO限制)

    linux作为服务器系统,当运行高并发TCP程序时,通常会出现连接建立到一定个数后不能再建立连接的情况 本人在工作时,测试高并发tcp程序(GPS服务器端程序),多次测试,发现每次连接建立到3800左 ...