在SCADA系统画面显示中,最重要的技术就是画面图形刷新技术,如果按照教科书上,如《MFC深入浅出》中的列子来写绘图程序,图元一多就会出现图形绘制时存在着图元绘制速度慢,使得画面整屏刷新时,在开发态会逐个显示图元,而运行态出现白屏现象。如何对该缺陷进行改进,具体工作内容需根据实际情况而定,下面提出一种基本的画面图形快速绘制技术,以供参考。

      组态软件要实现图元的鼠标交互绘制,并且在运行中需对实时数据以比较高的频率进行屏幕动态刷新显示(一般每50ms刷新一次),因此,对画面窗口绘制实现要求比较高,在此设计画面窗口绘制是以缓存区域刷新两种技术的组合来实现。

       缓存技术是指在画面窗口绘制过程中,不是直接将各图元的绘制效果显示在屏幕上,而是首先将窗口画面以位图形式保存到内存,然后将各图元绘制到该缓存中,最后将缓存中信息重新写回画面窗口,整体显示,使得CRT不为每一个画面的变化而进行一次整屏刷新,从而实现画面窗口的高速绘制。区域刷新技术是指在上述操作过程中只对当前刷新区域进行处理,如仅复制当前区域内屏幕信息到内存,只对与当前刷新区域相容或相交的图元进行绘制,从而进一步提高画面窗口的绘制速度。如何创建memDC的文章网上已经很多,此次不再具体描述。

       在程序中,各函数通过逐阶调用或消息通知运行即可实现不同方式的画面窗口绘制。如图元操作函数调用Invalidate函数或CDrawView::InvalObj(this)函数即可实现实时显示本次图元变换操作效果;在视图编辑函数中通过调用OnUpdate函数即可实现不同模式的画面窗口绘制;在工程运行过程中,动画对象也可实现对动画显示区域的动态刷新,而不需整屏刷新,避免了屏幕闪烁,提高系统运行速度。建立画面窗口绘制函数调用关系图如图1.1所示。


      分析图1.1画面窗口绘制函数调用关系图,可以看到最终的绘制过程都由画面视图的OnDraw()函数汇总实现,主要有两个大支流,分别对应InvalidateRect()函数和Invalidate()函数,实现单个图元的区域绘制和整屏重绘,这两种绘制方式将根据不同情况而被应用,以达到最快的画面绘制效率。

 

单个图元绘制

    单个图元绘制指在图形开发/运行时,画面视图仅对变动的图元所占区域进行重绘,主要是采用区域绘制与缓存技术的结合方式实现。如图1.1所描述,单个图元的绘制主要发生在图元编辑操作和动画连接显示。

1.图元编辑操作时,通过逻辑模块定位该图元,调用本图元类的Invalidate()函数,宣布本图元所在区域无效,调用画面逻辑模块::UpdateAllViews在图元所在逻辑模块的所有视图中重绘本图元,各视图获取该图元指针,并重绘图元所占区域;对于单个画面视图刷新,也可直接将本图元指针传给所在视图,调用该视图:: InvalObj()函数,重绘图元所占区域。

2. 运行时进行动画刷新时,直接关联数据改变,需要动画刷新的图元指针传给所在画面视图,调用该视图:: InvalObj()函数,重绘图元所占区域。这个过程可采用两种方法,一种是在动画扫描时,每扫描到一个关联数据发生变化的动画对象,便在动画数据处理后,实现对应图元的重绘;另一种方法是全部扫描完该画面视图动画链表后对整屏进行重绘。二者各有优缺点,当图元重合度小或瞬间动画图元少时,采用第一种方式具有明显速度优势;但当图元重合度大时,因单个图元的绘是采用区域绘制技术,将会造成实际绘制图元数明显大于画面视图的图元总数,此时采用第二种方式全屏绘制反而能保证刷新速度。

局部刷新技术具体实现如下:

每个图元更新时调用pDC->InvalidataRect(m_sz),这个时候在画面刷新时将获得该刷新区域的剪裁区,其中剪裁区分为region 和 clipRect,区别见下图:

 

其中蓝色的区域合集为region,用GetUpdateRgn获得,红色的区域为所有region围成的最小矩形区,用dc.GetClipBox(clipBoxRect)获得。

在OnPaint函数中先获得该画面目前需要刷新的CRgn和Rect

 

代码
//获得所要刷新的区域
CRgn* pInvalidateRgn;
CRgn rgnInvalidate;
rgnInvalidate.CreateRectRgn(
0,0,0,0 );
int nRgnResult = this->GetUpdateRgn( &rgnInvalidate );
if ( nRgnResult != ERROR && nRgnResult != NULLREGION )
{
pInvalidateRgn
= &rgnInvalidate;
}
else
{
return;
}
//获得需要刷新的最小剪裁矩形
CRect clipBoxRect;
dc.GetClipBox(clipBoxRect);
if( clipBoxRect.IsRectEmpty() )
{
if ( rgnInvalidate.GetSafeHandle() )
{
rgnInvalidate.DeleteObject();
}
return;
}

 

 

将pInvalidateRgn和clipBoxRect以及memDC传入每个图元的绘制函数中,绘制前先判断该region和clipRect是否与该图元的边界有交集

 

Draw
/// <summary>

/// 判断是否与矩形相交,包括包含

/// </summary>

BOOL CLayer::IsIntersectsWith(
const CRect& rect1,const CRect& rect2 )

{

return ( (rect1.left < (rect2.left + rect2.Width()))

&& (rect1.top < (rect2.top + rect2.Height()))

&& (rect2.left < (rect1.left + rect1.Width()))

&& (rect2.top < (rect1.top + rect1.Height())) );

}

/// <summary>

/// 图层绘制函数

/// </summary>

void CLayer::Draw(CDC *pDC,CRect *pr,CRgn* pInvalidateRgn)

{

int i;

if(!m_enable) return ;



int cnt = m_FigObjArray.GetSize();

BOOL bl
=TRUE;

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

CObjBase
*pFigObj = m_FigObjArray.GetAt(i);

CRect pixelBounds(pFigObj
->m_pt0,pFigObj->m_sz+CSize(1,1));

///过滤不在剪裁区中的图元

if (!IsIntersectsWith(pixelBounds,*pr))

{

continue;

}
        ///过滤不在刷新区域中的图元

if (pInvalidateRgn)

{

pDC
->LPtoDP(&pixelBounds);

if (!pInvalidateRgn->RectInRegion( pixelBounds ))

{

continue;

}

}



///只有在刷新区域内的图元才真正绘制

bl
=pFigObj->DrawFig(pDC,bl);



}

}

 

 

最后将memDC的内容贴到屏幕的DC上,完成绘图

 

pDC->BitBlt(clipBoxRect.left,clipBoxRect.top,clipBoxRect.Width(),clipBoxRect.Height(),&mDC,0,0,SRCCOPY);

 

 

画面整屏刷新

画面整屏刷新是指进行画面视图重绘时,按该视图对应图元对象链表的层次关系,

由里向外逐个绘制到缓存中,完全绘制后,再将缓存画面刷新到屏幕上。因没有采用区域绘制技术,仅采用缓存技术,不会由于每个图元的重绘因图元相交而大量重绘不相干图元,这样的优点是能保证每次重绘全屏的速度相同。缺点是每次都需重绘所有图元,即使是几个图元属性参数发生变化。

画面整屏刷新主要是发生在画面窗口切换、初始化和画面窗口的属性编辑。