VisualC++实现的函数波形观察控件

来源:互联网 发布:sql varchar 最大长度 编辑:程序博客网 时间:2024/05/17 01:56

采用Visual C++实现的函数波形显示控件

高宏亮,王淑娟,翟国富

                      (哈尔滨工业大学军用电器研究所 哈尔滨 150001

  要: 波形显示控件是工业控制类软件常用的一种控件,本文采用Visual C++设计并实现了基于CButton自绘方式的函数波形显示控件,该控件以位图双缓冲方式显示波形,实现了大数据量波形的显示,且具有类似matlab中波形显示窗口一样任意尺度局部放大的功能,同时针对一般控件中大数据量显示控件界面反映迟缓的问题提出改进方法,本文进一步加快界面的响应速度,实现了较好的效果。

关键词: Visual C++;双缓冲;波形显示;任意尺度放大

Design and Implementation of wave-watching control using Visual C++

Gao Hong-liang, Wang Shu-juan,Zhai Guo-fu

(Department of Electrical Engineering, Harbin Institute of Technology, Harbin 150001, China)

0 引言

目前,国内外许多公司都推出了适用于开发工业监控软件的软件产品,如美国NI公司的labviewWonderware InTouch , Interllution 公司的FixSimens 公司的WinCC等;国内主要有组态王、华富计算机公司的ControX,以及台湾研华的Genie 等。这些软件的功能齐全,但价格都很昂贵,对于一些小型用户并不适用,而且一般此类如软件都以ActiveX控件形式发布,不便于用户根据具体需求修改控件功能。

而目前一些资料及网络上提供的显示曲线的控件,功能大都是较简单,主要显示实时采集数据的曲线,用于观察数据的变化趋势,此类控件多以每个采样点对应一个显示像素宽度的比例显示曲线,通过波形推移方式显示全部数据,无法显示大量数据的整体波形,并且无波形缩放功能[1]。而对于一些数据分析类软件需要对长串数组的总体显示及任意尺度细节的放大,类似于matlabplot函数波形显示功能。本文实现的波形显示控件,主要完成数据动态刷新,全局显示,任意尺度放大等功能,并针对常见软件中大数据量显示中出现的界面迟滞问题给出了优化解决办法。

1 基本功能描述及实现算法

为了方便响应各种鼠标输入和图形输出控制,本控件采用CButton派生类自绘方式实现,这样实现的控件与用户程序方便集成,且用户可根据自己情况作出修改(如背景色,坐标字号等),界面效果如图1所示。

 

 

1波形显示控件全局显示波形

1.1基本图形显示功能

由于控件是基于自绘控件方式实现,控件的主要图形绘制功能都在CButton::DrawItem 函数中实现。

图形显示控件的常见问题是界面户刷新时的闪烁问题,为了消除闪烁,本控件显示采用了位图双缓冲显示技术,主要步骤为:先将显示内容绘制在一个内存位图对象上,用CDC::Bitblt()函数将位图直接输出,由于BitbltWindows系统中函数优先级较高,故可实现极快速的显示,消除闪烁。为了操作方便,本文参考文献[2]中定义,将关于双缓冲的操作封装成一个名为CMemDC的类负责内存位图的创建与显示输出。使用CMemDC对象绘图时代码的格式如下:

Void CDispPanel::DrawItem          (LPDRAWITEMSTRUCTlpDrawItemStruct){

CRect ClientRect=lpDrawItemStruct-> rcItem ;//获得控件所占整个矩形大小

CDC*pDC=CDC::FromHandle (lpDrawItemStruct->hDC);

//获得控件显示DC

ClearUnits(pDC);//用对话框背景色清除坐标显示区域,并刷新显示坐标

CRect m_GraphRect = ClientRect;

m_GraphRect.DeflateRect(10,10,20,20);//缩小控件矩形框,得到波形显示矩形大小

CMemDC*pMemDC=new CMemDC (pDC,&m_GraphRect);

//创建双缓冲用位图,并选中该对象

pMemDC->FillRect(&m_GraphRect,&CBrush (RGB(255,255,255)));//先填充绘

//图区背景色(白色)

………//其他绘图操作

delete pMemDC;//销毁对象操作中包含显示输出双缓冲位图

}

一般图形显示控件中都需要坐标标注部分,即图形显示部分背景色与窗口界面背景色不同,图1中显示了实际按钮资源的大小。由于波形显示区域与坐标显示区域的背景色不同,如将整个控件举行区域作为双缓冲显示矩形,不同的颜色背景的刷新会造成图形的闪烁,为此,定义了m_GraphRect对象来定义波形显示矩形大小,并以此建立双缓冲位图。由于坐标显示区域的图形变化并不复杂,界面刷新只用背景色的矩形覆盖即可。

CRect m_GraphRect = ClientRect;

m_GraphRect.DeflateRect(10,10,20,20);

//缩小控件矩形框,得到波形显示矩形大小。

1.2任意长度曲线窗口映射算法

根据程序界面的不同要求,波形显示控件的宽度不尽不同,要实现任意长度曲线的全局现实功能,就需要建立灵活的坐标映射机制,将整个波形缩放至任意宽度窗口中显示。

设显示波形数组为doubleYData[](由控件外部调用函数负责内存管理),窗口宽度为Widthwin,显示数据总长度位TotalLength,设波形数据每一点的对应窗口中坐标Xwin为,计算公式如式1所示。

 

 

2 坐标映射算法示意图

                                       (1)

                                      (i=0,1…TotalLength)其中[]为取整运算

*ZoomScale       (2)

              其中:ZoomScale为纵轴方向放大倍数;

可以看出由于显示屏幕上最小显示分辨单位为像素,Xwin(i)必须取整数,当显示数据长度远大于窗口长度时,会有很多数据灰重叠显示在一个横坐标位置上,同理,当显示数据长度远小于窗口长度时,会有很多窗口横坐标显示同一个数据[3]

1.3任意尺度细节放大功能实现

当鼠标左键托放时,首先需要能动态实时显示放大框,在鼠标左键释放时,计算放大区域,并显示响应数据。

实现过程中主要用到的数据对象如下:

CRect     CurFrameRect;//记录当前需要放大的矩形框的位置

CRect     lastFrameRect;//记录当前放大之前整个显示区域代表的矩形区域大小(多次放大时用到)

3鼠标左键托动时放大框显示

int indexZoomBegin,indexZoomEnd;//放大区域对应波形数据中的起始位置索引。

鼠标左键按下时,函数主要完成功能包括:确定放大框的最上角位置并置放大框矩形大小为0。特别需要说明,函数返回参数point为相对于按钮区域左上角为零点的相对位置,而不是相对m_GraphRect位置,需要进行坐标点转换。

void CDispPanel::OnLButtonDown(UINT nFlags, CPoint point) {

if(point.x<leftMarginX) return;

//放大框起始点落在绘图区以外,退出

if(m_pTotalYdata==NULL) return;

//无显示数据,退出

showFrame = true;

//显示放大框标志位置真

CurFrameRect.top = point.y-topMarginY;

CurFrameRect.bottom=CurFrameRect.top;

CurFrameRect.left =point.x-leftMarginX;

CurFrameRect.right =CurFrameRect.left;

//获得当前放大框左上角位置

SetCapture();//设置鼠标捕捉开关

CButton::OnLButtonDown(nFlags,point);

}

鼠标移动时,函数主要完成功能包括:如果当前状态处于放大状态,则动态修改放大框大小,修改放大框矩形右下角最标位置。

鼠标左键抬起时,函数主要完成功能包括:根据放大框大小,位置,计算响应的显示数据区间,并刷新显示数据。保存此次放大框参数值为lastFrameRect,以便多次放大时使用。

void CDispPanel::OnLButtonUp(UINT nFlags, CPoint point) {

if(m_pTotalYdata==NULL) return;

//无显示数据,退出

if(CurFrameRect.Width()<1||

CurFrameRect.Height() < 1) return;

//放大框尺度为0,退出

if (showFrame)     showFrame = false;

int OffsetBegin,OffsetEnd;

if(Zoomed||TotalLength>=m_GraphRect.Width()){

OffsetBegin= int(lastFrameRect.left+1.0*CurFrameRect.left/m_GraphRect.Width()*lastFrameRect.Width());

OffsetEnd = int(lastFrameRect.left+1.0*CurFrameRect.right/m_GraphRect.Width()*lastFrameRect.Width());

}//计算放大区对应数据在整个数据数组中的位置

else  {//数据量小于窗口宽度且不放大波形

if(CurFrameRect.left>=TotalLength) return;

if(CurFrameRect.right>TotalLength) CurFrameRect.right=TotalLength;

OffsetBegin= int(lastFrameRect.left+1.0*CurFrameRect.left);

OffsetEnd = int(lastFrameRect.left+1.0*CurFrameRect.right);

}

if(OffsetBegin<0) OffsetBegin=0;

if(OffsetEnd>TotalLength) OffsetEnd = TotalLength;

CurLength = OffsetEnd - OffsetBegin;

if (CurFrameRect.Height()>1){

scaleY*=1.0*m_GraphRect.Height()/CurFrameRect.Height();

}

lastFrameRect.left = OffsetBegin;

lastFrameRect.right = OffsetEnd;

Zoomed = true;

Invalidate(FALSE);

CButton::OnLButtonUp(nFlags, point);

}

鼠标右键按下时,函数主要完成功能包括:退出局部放大模式,清空放大框矩形内容,关闭放大标志位,显示数据波形全貌。图4显示了多次放大后某段数据的波形显示。

4波形任意尺度细节放大显示

2 性能优化部分

1.1节中可以看出,当显示数据长度大于窗口宽度10倍时,经过公式1中的取整运算后,窗口一个水平像素位置上有很多数据点重叠绘制,而这些重复点对于波形显示效果时影响不大的,主要由一组数据中的最大值、最小值决定,而且,由于数据量的过大,显示速度有一定影响,为加快界面显示速度,作者提出分类处理的方法,加快刷新速度。具体办法是:根据显示数据长度m_nCurDataLen与窗口长度m_nWinWidth之比进行不同的处理,

       m_nCurDataLen >10*m_nWinWidth :将显示数据中对应显示在同一水平像素位置的数据分为一组,搜寻每组中数据的最大值和最小值,显示时只画最大值和最小值于同一水平像素点,其余点不画;

       m_nCurDataLen <10*m_nWinWidth:按正常公式1算法,依次画每一个数据点对应的位置

    当显示数据全部长度小于窗口长度时,且处于非放大状态显示数据原貌时,直接按照一个像素位置显示一个数据方式显示。

具体代码结构为:

       double tempMin=0,tempMax=0;;

       if (CurLength>10*pRect->Width())

       {//数据量是窗口宽度的10倍以上

       nStep=1.0*CurLength/(pRect->Width());//计算每组数据长度

       for(x=0,i=0;i<pRect->Width();i++,x=(int)(x+nStep)){

       tempMin=MinA(&m_pCurYdata[int(x)],int(nStep));

       tempMax=MaxA(&m_pCurYdata[int(x)],int(nStep));

       if(tempMin<0)pDC->LineTo(x0+i,(int)(y0-tempMin*scaleY));

       if(tempMax>0)pDC->LineTo(x0+i,(int)(y0-tempMax*scaleY));

       if(tempMin == 0 && tempMax == 0) pDC->LineTo(x0+i,y0);

       }

}

       else {//数据量是窗口的10倍以下或者数据量小于窗口尺寸

if(!Zoomed&&CurLength<pRect->Width()){//不是放大状态,且数据量小于窗//口宽度

       for(i=0;i<CurLength&&i<pRect->Width();i++){

       pDC->LineTo((int)(x0+i),(int)(y0-m_pCurYdata[i]*scaleY));

       }

       }

       else{

if(CurLength>0){

       nStep = 1.0*pRect->Width()/CurLength; //计算每组数据长度

       for (i=0;i<CurLength;i++){

       pDC->LineTo((int)(x0+i*nStep),(int)(y0-m_pCurYdata[i]*scaleY));

              }

       }

}

       }

3 控件测试

       由于本控件直接采用MFCCButton类扩展实现控件,使用控件时只需将源代码文件加入工程项目,界面中创建按钮控件并将按钮与控件类绑定即可。经作者试验,该控件加在5000,000长度数据显示波形时,仍可以实现快速,流畅的波形显示功能。

4 结论

       本文采用Visual C++ 编程工具实现了用于函数波形观察的控件,控件基于按钮自绘方式实现,采用位图双缓冲输出方式实现无闪烁显示,适用于单维数据的全局和局部任意尺度放大显示,对于某些大数据量波形显示刷新缓慢的问题,给出了优化改进办法,实现了较好的显示效果。

参考文献

[1] 魏庆勇,王阳明,陈久康.VC 环境下工业监控软件趋势曲线显示画面的实现”, 机电一体化,2001(66264.

[2] 编程高手工作室,Visual C++ 编程高手》, 2000,137139.

[3] Michael J1Young. Visual C ++ 6 从入门到精通. 北京:电子工业出版社,1998

 

本文发表于《电测与仪表》2006年第12期

下面链接是文章对应源代码和期刊网文章下载,欢迎提出批评意见。

http://download.csdn.net/source/359781