url:http://tech.ddvip.com/2008-03/120660597243742.html
从毕业到现在已经有比较长的一段时间了,在这些工作期间,编写工控软件一直是我主要从事的职业。由于当时所在的是一个中小心公司,主要负责电力行业的监控系统,同时开拓一些工控相关的业务,比如自来水,闸门,橡胶坝等系统。这类系统一个显著的特点就是系统集成度比较高,要求对现场的控制要求比较了解,然后采用PLC+工控机的模式就可以了。现在的PLC品种繁多,而且技术都非常成熟,能够稳定运行,且各PLC厂商都提供了编程软件,能够快速的进行开发,而在上位机(工控机软件)部分也有不少的组态软件提供商,比较有名的诸如组态王、Visual Graph、iFix等。但是这些组态软件很多都是根据项目授权,即投运一个项目需要得到他的一个授权。作为中小心公司,公司领导层觉得既然拥有自己的软件开发工程师如果再采用组态软件来实现的话是一笔很划不来的事情,所以公司的主张就是自己开发自己的监控软件。
开发工控软件很重要的一个部分就是图形系统。很多组态系统提供了丰富的图形组态功能,能够通过他们提供的图形库完成各种复杂的图形开发,而且效果非常好。比如表记,LED,趋势,流动控件等。而自己开发则要单调很多。现在有有了一些诸如世纪飞扬等控件提供商,提供一系列控件开发包,不过一句话,他们都是要花米米的。作为很多开发人员是不愿意去出这些钱的,但是搜索网络上面这类控件,却几乎难得找到几个。前些时候一个朋友接了一个项目,一个泵厂的监控系统,这个系统实现中需要很多的管道和管道内部趋势图,能够实时反映水流的方向等。由于属于私人开发不可能花钱去购买那些控件,于是要我给他做一个可用于这个项目能够反映流动情况的管道控件。
多亏了现在网络上信息的丰富,“如果说我成功那是因为我站在了巨人的肩膀上“,通过考虑,我们可以采用管道和流动相结合的方法来实现我们的控件
首先是管道,管道简单考虑就是横和竖两种(至于您有时间可以考虑那些七七八八转弯的情况),那么我们这里可以把管道定义成两种模式
typedef enum _FlowPattern
然后是里面的水流的流动方向,横向的可以为由左向右和由右向左,竖向的可以分为由上向下和右下向上,这样我们就可以抽象里面的流动方向:
{
FP_HORIZONTAL = 0,
FP_VERTICAL = 1
}FLOWPATTERN;typedef enum _DirectionType
{
LEFT_TO_RIGHT = 0,
RIGHT_TO_LEFT = 1,
TOP_TO_BOTTOM = 2,
BOTTOM_TO_TOP = 3
}DIRECTIONTYPE;
下面就是管道的绘制了,如何绘制才能达到视觉上的管道效果呢。如果是一个平面矩形是不能看起来视觉效果的管道的。参考了网络上面的一些渐变的处理方法后发现,其实我们的管道可以用这个方法来实现。如果管道的边和中央用两种不同的颜色,这两种颜色为一种颜色近似的值,一种深,一种浅。当颜色处理由边向中央绘制采用的方法为:浅—深—浅时,这样我们可以产生视觉效果的管道的凹表面,反过来则能够产生管道的凸表面。现在网络上面如何进行渐变绘图的方法实例很多,这个是没有问题的。
然后就是我们要考虑的流动的设计了。在我们平常见到过的一些流动控件中(收费控件)流动的形状可以为多种,有的是矩形,有的是小三角形等。而流动的表现就是在管道的中间绘制一些我们上面举例的小图形等。这样,我们为这些单个的小图形设计一个功能类,然后在类中间实现不同的单个小滑块进行绘制,如果我们的管道有N个这样的小滑块组成,如果我们进行动态的绘制小滑块,那么不就会产生视觉上的滑动了。
原理诸如上面所需,我们在滑动的控件中设计一个定时器,利用定时器驱动,然后将管道利用形状动态填充一个滑块列队,每次动态计算每个滑块的位置,通过动态刷新管道背景和滑块位置(擦除掉原来的那些旧的滑块位置)就可以产生滑动的效果了,管道流动控件原理基本上就是这样了。
下面是动手实现了,我们首先通过ClassWizard创建一个继承CStatic的管道流动类CFlowCtrl,然后映射WM_PAINT消息,因为在这里要完成管道和滑块的绘制。如果我们的定时器时间很小,刷新频率很快,将会出现很严重的闪烁现象,这里我们采用双缓冲,在内存中间进行复杂的图形的绘制,然后进行“贴“(BitBtn)到界面上就可以不闪烁了。为了使程序的可读性增强,我们对管道绘制和滑块绘制分两个类(CFlowImpl、CPipeImpl)来进行。
管道绘制:
采用渐变的方法进行管道的绘制,由于在绘图过程中,这里不是经常变化的,只有当管道的颜色发生变化或者其他一些管道的配置发生变化才要重绘,我们在双缓冲绘图时只要进行一次绘图就可以了,然后生成绘图句柄到内存,这样可以提高效率。
void CPipeImpl::Draw(CDC *pDC)
绘制渐变我们采用一个常用的方法,这里进行这个方法的简单的封装,以后还能够用这些方法绘制图形的渐变区域以及工业控制别的控件,比如容器等。
{
//用渐变的方法绘制管道
DrawGradient(pDC);
//绘制图形周围的边框
CRect rcClient;
m_pOwer->GetClientRect(&rcClient);
CBrush pBlackBrush(m_colorBorder);
pDC->FrameRect(&rcClient, &pBlackBrush);
}void CPipeImpl::DrawGradient(CDC *pDC)
下面是滑块的绘制,根据开始的原理,我们对每个滑块抽象一个小类CFlowUnit,根据滑块的形状我们可以抽象滑块的外观:
{
CRect rcClient;
CBitmap MemBmp, *pOldMemBmp;
CDC MemDC;
COLORREF crCur;
CPoint ptCur;
m_pOwer->GetClientRect(&rcClient);
int nWidth = m_pOwer->GetFlowPattern() == FP_HORIZONTAL ?
rcClient.Height() : rcClient.Width();
RGBTRIPLE *pRGBTriple = new RGBTRIPLE[nWidth], *pEntry;
if (pRGBTriple == NULL)
{
return;
}
MemBmp.CreateCompatibleBitmap(pDC,
m_pOwer->GetFlowPattern() == FP_HORIZONTAL ? 1 : rcClient.Width(),
m_pOwer->GetFlowPattern() == FP_HORIZONTAL ? rcClient.Height() : 1);
MemDC.CreateCompatibleDC(pDC);
pOldMemBmp = (CBitmap *)MemDC.SelectObject(&MemBmp);
ptCur = CPoint(0, 0);
m_Gradient.MakeEntries(pRGBTriple, nWidth);
for (int i=0; i<nWidth; i++)
{
if (m_pOwer->GetFlowPattern() == FP_HORIZONTAL)
{
ptCur.y = i;
}
else
{
ptCur.x = i;
}
pEntry = &pRGBTriple[i];
crCur = RGB(pEntry->rgbtRed, pEntry->rgbtGreen, pEntry->rgbtBlue);
MemDC.SetPixelV(ptCur, crCur);
}
if (m_pOwer->GetFlowPattern() == FP_HORIZONTAL)
{
pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&MemDC, 0, 0, 1, nWidth, SRCCOPY);
}
else
{
pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&MemDC, 0, 0, nWidth, 1, SRCCOPY);
}
MemDC.SelectObject(pOldMemBmp);
MemBmp.DeleteObject();
MemDC.DeleteDC();
delete [] pRGBTriple;
}
FlowCtrl在OnPaint函数中通过调用PipeImpl的Draw就能够完成背景的绘制了。typedef enum _UnitPattern
然后实现一个Draw函数完成这些不同形状的滑块的绘制工作。
{
UP_RECTANGLE = 0,
UP_CIRCLE = 1,
UP_DLINE = 2, // 象 >> 类别
UP_TRIANGLE = 3
}UNITPATTERN;void CFlowUnit::Draw(CDC *pDC, CRect &rcClient)
剩下来的就是我们实现滑块的位置控制了。我们可以采用一个简单的实现方法,比如采用每个定时器到达的时候移动几个像素点的方法,通过向管道里面的滑块刘表传递下一个区域就可以了。当这个区域处于滑出去的区域时候,我们自动销毁这个滑块对象,然后重新添加一个滑块对象到最开始的地方就可以了。 我们将在FlowImpl中定义一个CList<CFlowUnit *, CFlowUnit *> m_arrayFlowUnit;链表,然后在初始化的时候通过滑块的长,宽等信息计算管道可以容纳的滑块数量和位置。
{
CBrush pBackBrush, *pOldBrush;
if (m_pParentWnd == NULL)
{
return;
}
pBackBrush.CreateSolidBrush(m_pParentWnd->GetUnitBackColor());
pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
switch (m_pParentWnd->GetUnitPattern())
{
case UP_RECTANGLE:
pDC->FillRect(&m_rectClient, &pBackBrush);
break;
case UP_CIRCLE:
pDC->SelectObject(::GetStockObject(NULL_PEN));
pDC->Ellipse(&m_rectClient);
break;
case UP_DLINE:
{
int xCenter = 0, yCenter = 0;
if ((m_pParentWnd->GetUnitDLinePattern() == DP_RIGHT) || (m_pParentWnd->GetUnitDLinePattern() == DP_LEFT))
{
xCenter = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
yCenter = (rcClient.bottom - rcClient.top) / 2;
}
else
{
xCenter = (rcClient.right - rcClient.left) / 2;
yCenter = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
}
DrawDLine(pDC, CPoint(xCenter, yCenter));
}
break;
case UP_TRIANGLE:
pDC->SelectObject(::GetStockObject(NULL_PEN));
DrawTriangle(pDC);
break;
}
pDC->SelectObject(pOldBrush);
pBackBrush.DeleteObject();
}
void CFlowUnit::DrawDLine(CDC *pDC, CPoint ptCenter)
{
if (m_pParentWnd == NULL)
{
return;
}
switch (m_pParentWnd->GetUnitDLinePattern())
{
case DP_RIGHT:
_D_LINE_H(ptCenter.x + 0, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 2, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 1, ptCenter.y + 3, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 0, ptCenter.y + 4, m_pParentWnd->GetUnitBackColor());
break;
case DP_LEFT:
_D_LINE_H(ptCenter.x + 2, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 0, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 1, ptCenter.y + 3, m_pParentWnd->GetUnitBackColor());
_D_LINE_H(ptCenter.x + 2, ptCenter.y + 4, m_pParentWnd->GetUnitBackColor());
break;
case DP_DOWN:
_D_LINE_V(ptCenter.x + 0, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 2, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 3, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 4, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
break;
case DP_UP:
_D_LINE_V(ptCenter.x + 0, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 1, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 2, ptCenter.y + 0, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 3, ptCenter.y + 1, m_pParentWnd->GetUnitBackColor());
_D_LINE_V(ptCenter.x + 4, ptCenter.y + 2, m_pParentWnd->GetUnitBackColor());
break;
default:
break;
}
}
void CFlowUnit::DrawTriangle(CDC *pDC)
{
if (m_pParentWnd == NULL)
{
return;
}
CPoint ptRgn[3];
switch (m_pParentWnd->GetFlowCtrl()->GetDirectionType())
{
case LEFT_TO_RIGHT:
ptRgn[0].x = m_rectClient.left;
ptRgn[0].y = m_rectClient.top;
ptRgn[1].x = m_rectClient.left;
ptRgn[1].y = m_rectClient.bottom;
ptRgn[2].x = m_rectClient.right;
ptRgn[2].y = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
break;
case RIGHT_TO_LEFT:
ptRgn[0].x = m_rectClient.right;
ptRgn[0].y = m_rectClient.top;
ptRgn[1].x = m_rectClient.right;
ptRgn[1].y = m_rectClient.bottom;
ptRgn[2].x = m_rectClient.left;
ptRgn[2].y = m_rectClient.top + (m_rectClient.bottom - m_rectClient.top) / 2;
break;
case TOP_TO_BOTTOM:
ptRgn[0].x = m_rectClient.left;
ptRgn[0].y = m_rectClient.top;
ptRgn[1].x = m_rectClient.right;
ptRgn[1].y = m_rectClient.top;
ptRgn[2].x = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
ptRgn[2].y = m_rectClient.bottom;
break;
case BOTTOM_TO_TOP:
ptRgn[0].x = m_rectClient.left;
ptRgn[0].y = m_rectClient.bottom;
ptRgn[1].x = m_rectClient.right;
ptRgn[1].y = m_rectClient.bottom;
ptRgn[2].x = m_rectClient.left + (m_rectClient.right - m_rectClient.left) / 2;
ptRgn[2].y = m_rectClient.top;
break;
default:
break;
}
CBrush pBackBrush, *pOldBrush;
pBackBrush.CreateSolidBrush(m_pParentWnd->GetUnitBackColor());
pOldBrush = (CBrush *)pDC->SelectObject(&pBackBrush);
pDC->SetPolyFillMode(WINDING);
pDC->Polygon(ptRgn, 3);
pDC->SelectObject(pOldBrush);
pBackBrush.DeleteObject();
}void CFlowImpl::InitFlowUnit()
然后通过定时器到达的时候动态计算链表滑块的每个对象的下一个要显示的CRect,然后进行管道背景刷新就可以完成了。
{
CRect rcUnit, rcClient;
m_pOwer->GetClientRect(&rcClient);
m_arrayFlowUnit.RemoveAll();
if (m_pOwer->GetDirectionType() == LEFT_TO_RIGHT
|| m_pOwer->GetDirectionType() == RIGHT_TO_LEFT)
{
int nUnits = rcClient.Width() / (UNIT_WIDTH + UNIT_DISC);
int nResidue = (rcClient.bottom - rcClient.top) % 2;
int yCenter = (rcClient.bottom - rcClient.top) / 2;
for (int i=0; i<nUnits; i++)
{
rcUnit.SetRect(rcClient.left + i * (UNIT_WIDTH + UNIT_DISC),
yCenter - UNIT_HEIGHT / 2,
rcClient.left + i * (UNIT_WIDTH + UNIT_DISC) + UNIT_WIDTH,
yCenter + UNIT_HEIGHT / 2 + nResidue);
CFlowUnit *pFlowUnit = new CFlowUnit();
pFlowUnit->SetRect(rcUnit);
pFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddTail(pFlowUnit);
}
}
else
{
int nUnits = rcClient.bottom / (UNIT_WIDTH + UNIT_DISC);
int nResidue = (rcClient.right - rcClient.left) % 2;
int xCenter = (rcClient.right - rcClient.left) / 2;
for (int i=0; i<nUnits; i++)
{
rcUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
rcClient.top + i * (UNIT_WIDTH + UNIT_DISC),
xCenter + UNIT_HEIGHT / 2 + nResidue,
rcClient.top + i * (UNIT_WIDTH + UNIT_DISC) + UNIT_WIDTH);
CFlowUnit *pFlowUnit = new CFlowUnit();
pFlowUnit->SetRect(rcUnit);
pFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddTail(pFlowUnit);
}
}
}void CFlowImpl::CalculateFlowUnit()
最后我们定义一些控件的接口完成对控件的参数的设置。
{
CRect rcClient;
m_pOwer->GetClientRect(&rcClient);
int xResidue = (rcClient.right - rcClient.left) % 2;
int xCenter = (rcClient.right - rcClient.left) / 2;
int yCenter = (rcClient.bottom - rcClient.top) / 2;
int yResidue = (rcClient.bottom - rcClient.top) % 2;
POSITION pos = m_arrayFlowUnit.GetHeadPosition();
while (pos != NULL)
{
CFlowUnit *pFlowUnit = m_arrayFlowUnit.GetAt(pos);
//从左至右
if (m_pOwer->GetDirectionType() == LEFT_TO_RIGHT)
{
CRect rcUnit = pFlowUnit->GetRect();
rcUnit.DeflateRect(UNIT_STEP, 0, -UNIT_STEP, 0);
pFlowUnit->SetRect(rcUnit);
if (rcUnit.left >= rcClient.right)
{
pFlowUnit = m_arrayFlowUnit.GetHead();
CRect rcAddUnit;
rcAddUnit.SetRect(pFlowUnit->GetRect().left - UNIT_DISC - UNIT_WIDTH,
yCenter - UNIT_HEIGHT / 2,
pFlowUnit->GetRect().left - UNIT_DISC,
yCenter + UNIT_HEIGHT / 2 + yResidue);
CFlowUnit *pAddFlowUnit = new CFlowUnit();
pAddFlowUnit->SetRect(rcAddUnit);
pAddFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddHead(pAddFlowUnit);
m_arrayFlowUnit.RemoveAt(pos);
break;
}
}
//从右至左
else if (m_pOwer->GetDirectionType() == RIGHT_TO_LEFT)
{
CRect rcUnit = pFlowUnit->GetRect();
rcUnit.DeflateRect(-UNIT_STEP, 0, +UNIT_STEP, 0);
pFlowUnit->SetRect(rcUnit);
if (rcUnit.right <= rcClient.left)
{
pFlowUnit = m_arrayFlowUnit.GetTail();
CRect rcAddUnit;
rcAddUnit.SetRect(pFlowUnit->GetRect().right + UNIT_DISC,
yCenter - UNIT_HEIGHT / 2,
pFlowUnit->GetRect().right + UNIT_DISC + UNIT_WIDTH,
yCenter + UNIT_HEIGHT / 2 + yResidue);
CFlowUnit *pAddFlowUnit = new CFlowUnit();
pAddFlowUnit->SetRect(rcAddUnit);
pAddFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddTail(pAddFlowUnit);
m_arrayFlowUnit.RemoveAt(pos);
break;
}
}
//从上至下
else if (m_pOwer->GetDirectionType() == TOP_TO_BOTTOM)
{
CRect rcUnit = pFlowUnit->GetRect();
rcUnit.DeflateRect(0, UNIT_STEP, 0, -UNIT_STEP);
pFlowUnit->SetRect(rcUnit);
if (rcUnit.top >= rcClient.bottom)
{
pFlowUnit = m_arrayFlowUnit.GetHead();
CRect rcAddUnit;
rcAddUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
pFlowUnit->GetRect().top - UNIT_DISC - UNIT_WIDTH,
xCenter + UNIT_HEIGHT / 2 + xResidue,
pFlowUnit->GetRect().top - UNIT_DISC);
CFlowUnit *pAddFlowUnit = new CFlowUnit();
pAddFlowUnit->SetRect(rcAddUnit);
pAddFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddHead(pAddFlowUnit);
m_arrayFlowUnit.RemoveAt(pos);
break;
}
}
//从下至上
else if (m_pOwer->GetDirectionType() == BOTTOM_TO_TOP)
{
CRect rcUnit = pFlowUnit->GetRect();
rcUnit.DeflateRect(0, -UNIT_STEP, 0, +UNIT_STEP);
pFlowUnit->SetRect(rcUnit);
if (rcUnit.bottom <= rcClient.top)
{
pFlowUnit = m_arrayFlowUnit.GetTail();
CRect rcAddUnit;
rcAddUnit.SetRect(xCenter - UNIT_HEIGHT / 2,
pFlowUnit->GetRect().bottom + UNIT_DISC,
xCenter + UNIT_HEIGHT / 2 + xResidue,
pFlowUnit->GetRect().bottom + UNIT_DISC + UNIT_WIDTH);
CFlowUnit *pAddFlowUnit = new CFlowUnit();
pAddFlowUnit->SetRect(rcAddUnit);
pAddFlowUnit->SetParentWnd(this);
m_arrayFlowUnit.AddTail(pAddFlowUnit);
m_arrayFlowUnit.RemoveAt(pos);
break;
}
}
m_arrayFlowUnit.GetNext(pos);
}
}
我们可以看到程序的运行效果:
如果您还有更好的方法实现此类控件或者您已经实现了那些七七八八拐弯的功能,请您将您的经验写下来和我们分享。如果我们都能够分享自己的一些编程经验,那么我们的软件将会越来越人性化,越来越稳定,越来越好。