介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件。
正文
国内现在的工业组态软件很多都是采用组态软件,比如组态王,IFIX,图王等。而在提供控件开发包方面也有很多厂商提供,比如世纪飞扬的控件开发包,图王等。由于很多组态系统属于项目授权形式,针对每个项目进行单独授权。而在中小型公司系统实施过程中这个是一个不小的压力,所以很多公司都是自己编写适合自己公司产品的组态软件。而在编写工业控制软件系统中,相关的控件是必不可少的,比如使用仪表仿真比直接数字显示更能够逼真的反应现在情况的模拟。
现在网络上面常用的编写此类控件的方法是采用双缓冲的技术。双缓冲的原理可以这样形象的理解:把电脑屏幕看作一块黑板。首先我们在内存环境中建立一个“虚拟“的黑板,然后在这块黑板上绘制复杂的图形,等图形全部绘制完毕的时候,再一次性的把内存中绘制好的图形“拷贝”到另一块黑板(屏幕)上。采取这种方法可以提高绘图速度,极大的改善绘图效果。
我们这里建立一个新类CDiscMeter集成CStatic,然后映射WM_PAINT消息,然后在OnPaint中间实现图形的绘制工作。
void CDiscMeter::OnPaint()
{
CPaintDC dc(this); // device context for painting
// 获得控件区域
GetClientRect (&m_rectCtrl);
CDiscMemDC memDC(&dc, &m_rectCtrl);
//绘制仪表盘
if (m_dcMeterPlate.GetSafeHdc() == NULL || (m_bitmapMeterPlate.m_hObject == NULL))
{
m_dcMeterPlate.CreateCompatibleDC(&dc);
m_bitmapMeterPlate.CreateCompatibleBitmap(&dc, m_rectCtrl.Width(), m_rectCtrl.Height()) ;
m_pbitmapOldMeterPlate = m_dcMeterPlate.SelectObject(&m_bitmapMeterPlate) ;
DrawMeterBackground(&m_dcMeterPlate, m_rectCtrl);
}
memDC.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(),
&m_dcMeterPlate, 0, 0, SRCCOPY);
DrawNeedle(&memDC);
DrawValue(&memDC);
}
由于我们的背景不是实时变化的,我们在第一次运行的时候将背景的绘制完成,在以后每次调用Invalidate的时候就直接调用CDC的BitBtn方法完成背景的重绘工作。
在绘制背景程序中,我们根据弧度等计算方法,动态计算仪表的外各个背景的部位的位置,然后调用CDC类对象的一系列方法完成图形的绘制
//绘制仪表背景
void CDiscMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
CPen m_penMeter, *pOldPen;
CBrush m_brushBack, *pOldBrush;
pDC->SetBkColor(m_BackColor);
m_brushBack.CreateSolidBrush(m_BackColor);
pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);
pDC->FillRect(rect, &m_brushBack); //绘制背景
pDC->Rectangle(rect); //绘制一个边框
pDC->SelectObject(pOldBrush);
m_brushBack.DeleteObject();
m_penMeter.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&m_penMeter);
pDC->SetTextColor(RGB( 0, 0, 0));
pDC->SetBkMode(TRANSPARENT);
int nTmpLong = __min(rect.Width(), rect.Height());
m_ptMeterCenter.x = nTmpLong / 2; //点中心x坐标
m_ptMeterCenter.y = nTmpLong / 2; //点中心y坐标
m_nRadiusFrame = nTmpLong / 2 - 3;
//绘制仪表圆盘
CRect rectRound(m_ptMeterCenter.x - m_nRadiusFrame,
m_ptMeterCenter.y + m_nRadiusFrame,
m_ptMeterCenter.x + m_nRadiusFrame,
m_ptMeterCenter.y - m_nRadiusFrame);
pDC->Ellipse(rectRound);
//画书写当前值的内框
CPen pInnerFramePen, *pOldInnerFramePen;
pInnerFramePen.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
pOldInnerFramePen = (CPen *)pDC->SelectObject(&pInnerFramePen);
CRect rectInnerFrame(m_ptMeterCenter.x - m_nRadiusFrame * 9 / 20,
m_ptMeterCenter.y + m_nRadiusFrame * 6 / 10,
m_ptMeterCenter.x - m_nRadiusFrame * 9 / 20 + m_nRadiusFrame * 9 / 10,
m_ptMeterCenter.y + m_nRadiusFrame * 6 / 10 + m_nRadiusFrame * 5 / 20);
m_rectInnerFrame = rectInnerFrame;
pDC->FillSolidRect(rectInnerFrame, RGB( 230, 232, 232));
DrawRectangle(pDC, rectInnerFrame, RGB( 0, 0, 0));
rectInnerFrame.left ++;
rectInnerFrame.top ++;
rectInnerFrame.bottom --;
rectInnerFrame.right --;
DrawRectangle(pDC, rectInnerFrame, RGB( 255, 255, 255));
pDC->SelectObject(pOldInnerFramePen);
pInnerFramePen.DeleteObject();
//画外圈圆弧
int nTmpRadius = m_nRadiusFrame - 4;
CPoint ptBoundary, ptStart, ptEnd;
ptBoundary.x = int(sin(PI / 3) * nTmpRadius);
ptBoundary.y = int(cos(PI / 3) * nTmpRadius);
ptStart.x = m_ptMeterCenter.x + ptBoundary.x;
ptStart.y = m_ptMeterCenter.y + ptBoundary.y;
ptEnd.x = m_ptMeterCenter.x - ptBoundary.x;
ptEnd.y = m_ptMeterCenter.y + ptBoundary.y;
CRect arcAngle(m_ptMeterCenter.x - nTmpRadius,
m_ptMeterCenter.y - nTmpRadius,
m_ptMeterCenter.x + nTmpRadius,
m_ptMeterCenter.y + nTmpRadius);
pDC->Arc(&arcAngle, ptStart, ptEnd);
//画刻度
int nTicks = m_nTicks;
int nSubTicks = m_nSubTicks;
char strFigure[MAXNAMELENGTH + 1];
const int nSidePos = 40;
memset(strFigure, 0, sizeof(strFigure));
double dMaxAngle = double(240.00f / nTicks); //每个大格的角度
double dMinAnble = dMaxAngle / nSubTicks; //每个小格的角度
for (int i=0; i {
CPoint ptStartTick, ptEndTick;
ptStartTick.x = int(m_ptMeterCenter.x + (m_nRadiusFrame *
cos((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
ptStartTick.y = int(m_ptMeterCenter.y - (m_nRadiusFrame *
sin((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
ptEndTick.x = int(m_ptMeterCenter.x + ((m_nRadiusFrame - 10) *
cos((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
ptEndTick.y = int(m_ptMeterCenter.y - ((m_nRadiusFrame - 10) *
sin((dMaxAngle * (nTicks - i) - 30) * PI / 180.00f)));
pDC->MoveTo(ptStartTick);
pDC->LineTo(ptEndTick);
sprintf(strFigure, "%.1f", (m_dMaxValue - m_dMinValue) * i / nTicks);
if (dMaxAngle * (nTicks - i) - 30 < 60)
{
pDC->TextOut(ptEndTick.x - nSidePos, ptEndTick.y, strFigure);
}
else if (dMaxAngle * (nTicks - i) - 30 <= 90)
{
pDC->TextOut(ptEndTick.x - nSidePos / 2, ptEndTick.y + 3, strFigure);
}
else if (dMaxAngle * (nTicks - i) - 30 < 140)
{
pDC->TextOut(ptEndTick.x - nSidePos / 3, ptEndTick.y, strFigure);
}
else
{
pDC->TextOut(ptEndTick.x - nSidePos / 10, ptEndTick.y, strFigure);
}
}
for (i=0; i {
CPoint ptSubStartTick, ptSubEndTick;
ptSubStartTick.x = int(m_ptMeterCenter.x + (m_nRadiusFrame *
cos((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
ptSubStartTick.y = int(m_ptMeterCenter.y - (m_nRadiusFrame *
sin((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
ptSubEndTick.x = int(m_ptMeterCenter.x + ((m_nRadiusFrame - 6) *
cos((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
ptSubEndTick.y = int(m_ptMeterCenter.y - ((m_nRadiusFrame - 6) *
sin((dMinAnble * (nTicks * nSubTicks - i) - 30) * PI / 180.00f)));
pDC->MoveTo(ptSubStartTick);
pDC->LineTo(ptSubEndTick);
}
//文本格式的初始化
CFont pUnitFont, *pOldFont;
LOGFONT lf;
lf.lfEscapement = 0;
lf.lfItalic = NULL;
lf.lfUnderline = NULL;
lf.lfStrikeOut = NULL;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = m_nRadiusFrame / 5;
strcpy(lf.lfFaceName, "Impact");
pUnitFont.CreateFontIndirect(&lf);
pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
CRect rectUnits(int(m_ptMeterCenter.x - m_nRadiusFrame * 0.18f),
int(m_ptMeterCenter.y + m_nRadiusFrame * 0.25f),
int(m_ptMeterCenter.x - m_nRadiusFrame * 0.18f + m_nRadiusFrame *0.4f),
int(m_ptMeterCenter.y + m_nRadiusFrame * 0.25f + m_nRadiusFrame * 6 / 20));
pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SelectObject(pOldFont);
pUnitFont.DeleteObject();
pDC->SelectObject(pOldPen);
m_penMeter.DeleteObject();
}
由于控件进行数据显示的时候调用了CStatic类的Invalidate方法触发WM_PAINT消息,在完成背景的绘制后,我们需要完成控件的指针和实时值的显示,这样,我们的控件就完成了。
//绘制仪表指针
void CDiscMeter::DrawNeedle(CDC *pDC)
{
CBrush pCenterBrush, *pOldBrush;
if (m_dCurrentValue < m_dMinValue)
{
m_dCurrentValue = m_dMinValue;
}
else if (m_dCurrentValue > m_dMaxValue)
{
m_dCurrentValue = m_dMaxValue;
}
double dAngle = double(240.00f * ((m_dCurrentValue - m_dMinValue) /
(m_dMaxValue - m_dMinValue)));
int nTmpRadius1 = int(m_nRadiusFrame / 1.4f);
int nTmpRadius2 = m_nRadiusFrame / 6;
CRgn pRgn;
POINT ptAngle[3];
ptAngle[0].x = long(m_ptMeterCenter.x + nTmpRadius2 *
cos((210 - dAngle - 90) * PI / 180.00f));
ptAngle[0].y = long(m_ptMeterCenter.y - nTmpRadius2 *
sin((210 - dAngle - 90) * PI / 180.00f));
ptAngle[1].x = long(m_ptMeterCenter.x + nTmpRadius2 *
cos((210 - dAngle + 90) * PI / 180.00f));
ptAngle[1].y = long(m_ptMeterCenter.y - nTmpRadius2 *
sin((210 - dAngle + 90) * PI / 180.00f));
ptAngle[2].x = long(m_ptMeterCenter.x + nTmpRadius1 *
cos((210 - dAngle) * PI / 180.00f));
ptAngle[2].y = long(m_ptMeterCenter.y - nTmpRadius1 *
sin((210 - dAngle) * PI / 180.00f));
pRgn.CreatePolygonRgn(ptAngle, 3, ALTERNATE);
pDC->FillRgn(&pRgn, &CBrush(RGB( 0, 0, 0)));
pCenterBrush.CreateSolidBrush(RGB( 255, 0, 0));
pOldBrush = (CBrush *)pDC->SelectObject(&pCenterBrush);
CRect rectCenter(m_ptMeterCenter.x - nTmpRadius2,
m_ptMeterCenter.y - nTmpRadius2,
m_ptMeterCenter.x + nTmpRadius2,
m_ptMeterCenter.y + nTmpRadius2);
pDC->Ellipse(rectCenter);
pDC->SelectObject(pOldBrush);
pCenterBrush.DeleteObject();
}
//绘制仪表实时值
void CDiscMeter::DrawValue(CDC *pDC)
{
char strCurrentValue[10];
memset(strCurrentValue, 0, sizeof(strCurrentValue));
sprintf(strCurrentValue, "%.2f", m_dCurrentValue);
CFont pUnitFont, *pOldFont;
LOGFONT lf;
lf.lfEscapement = 0;
lf.lfItalic = NULL;
lf.lfUnderline = NULL;
lf.lfStrikeOut = NULL;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = m_nRadiusFrame / 6;
strcpy(lf.lfFaceName, "Arial");
pUnitFont.CreateFontIndirect(&lf);
pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
pDC->SetBkMode(TRANSPARENT);
pDC->DrawText(strCurrentValue, m_rectInnerFrame, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SelectObject(pOldFont);
pUnitFont.DeleteObject();
}