VC++大数据量绘图时无闪烁刷屏技术实现

2016-02-19 18:43 10 1 收藏

岁数大了,QQ也不闪了,微信也不响了,电话也不来了,但是图老师依旧坚持为大家推荐最精彩的内容,下面为大家精心准备的VC++大数据量绘图时无闪烁刷屏技术实现,希望大家看完后能赶快学习起来。

【 tulaoshi.com - 编程语言 】

  引言

  当我们需要在用户区显示一些图形时,先把图形在客户区画上,虽然已经画好但此时我们还无法看到,还要通过程序主动地刷新用户区,强制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。否则会造成系统资源的浪费。

  程序示例

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

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

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

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;irect.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;im_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;im_DX;i++)
   DrawPoint(value[i],RGB(0,255,0));
 }
 if(nIDEvent==1)
 {
  m_dx+=2;
  for(int i=0;im_DX;i++)
  {
   value[i].x=i;
   if(m_dx+i0)
    buffer[m_dx+i]=128;
   if(m_dx+i-m_DX)
    m_dx-=2;
   if(m_dx+im_BufLen)
    buffer[m_dx+i]=128;
   if(m_dx+im_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;im_DX;i++)
   DrawPoint(value[i],RGB(0,255,0));
  dc.SelectObject(pOldBitmap);
  ReleaseDC(pDC);
 }
 if(nIDEvent==1)
 {
  m_dx+=2;
  for(int i=0;im_DX;i++)
  {
   value[i].x=i;
   if(m_dx+i0)
    buffer[m_dx+i]=128;
   if(m_dx+i-m_DX)
    m_dx-=2;
   if(m_dx+im_BufLen)
    buffer[m_dx+i]=128;
   if(m_dx+im_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编译通过。

来源:https://www.tulaoshi.com/n/20160219/1618742.html

延伸阅读
    第二天:Microsoft基本类库应用程序框架 ◎MFC是C++的Microsoft Windows API,如果想要开发WINDOWS的应用程序当然VC/MFC是开发环境的首选。 ◎MFC产生的应用程序使用了标准化的结构。(我现在还体会不出这点的优势所在,请高手指点) ◎MFC产生的应用程序短而运行速度快。这应该说的是可以很容易的建立动态连接,其实程...
    第十篇:模式对话框和通用控件(下) 大家好,雷神由于出差在外,所以笔记今天才写出抱歉。不知道大家有没有做上篇提到的计算器,计算器对雷神来说可是经典的程序,学VB先搞了计算器,学VC也搞了个计算器,前不久在学扩展CBUTTON类时也是用计算器程序,为什么?按钮多呗,雷神最终做出了一个圆形按钮,液晶显示的计...
     第五篇:基本事件处理 我们已经知道MFC库应用程序框架调用CView视图类的虚函数OnDraw来完成屏幕显示。其实CView和CWnd类包含了几百个成员函数,在MSDN中可以看到这些成员函数,其中有许多On开头的,例如第二篇的例子就有一个OnLButtonDown,它们都是应用程序框架响应各种事件所需调用的函数。 OnDraw便...
第四篇:资源和编译 资源文件(就是以应用程序名和扩展名是.rc的文件)很大程度上决定了应用程序的用户界面。在VC++中资源文件包括以下内容: Accelerator //模拟菜单和工具栏的选择内容 Dialog //对话框的布局及内容 Icon //图标有两种一种是16X16一种是32X32。 Menu //应用程序的主菜单及所属的弹出式菜单 String table //字...
概述 管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。匿名管道(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信...

经验教程

931

收藏

74
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部