介绍
在工业控制系统开发过程中,图形显示方面占有着很重要的作用。比起很多专用的组态软件,他们有着强大的在图形系统,能够组态出来非常漂亮的系统。现在的很多的工业图形开发包都需要支付费用,很多漂亮的控件比如仪表等只能看图兴叹了。前些天一个朋友做一个泵站的监控系统,由于缺少相关的控件,在研究了该类控件的编程方法上,借鉴网络上的一些编程资料,完成了一些可用于工业控制系统开发使用的控件。
正文
在上一篇文章 http://www.vchelp.net/itbookreview/view_paper.asp?paper_id=1671中我们采用双缓冲绘图原理的方法,并结合网络上面的一些圆盘仪表的绘制技术完成了一个圆盘仪表的绘制。圆盘仪表绘制原理综合起来基本差不多,可以分为:绘制背景,绘制指针,绘制实时值等。这里,我们改变背景的显示式样,改变指针的显示式样和文字的显示式样,能够做出来更多类型的圆盘仪表。如果在我们的监控程序中使用这样的多类型的仪表,也会使得在软件的人机接口方面更丰富很多。
实现方法还是按照上一篇文章的方法,建立一个继承CStatic的基类,并映射WM_PAINT消息完成控件的双缓冲绘制过程。在绘制背景的过程中,我们可以采用一些技术使得背景更加的好看。比如在显示字体的时候我们可以利用在两个叠加的区域用两种不同的颜色显示同一个文字就能够达到叠加的效果。
比如我们在一个测试工程中间输入下面代码:
CFont pUnitFont, *pOldFont;
LOGFONT lf;
lf.lfEscapement = 0;
lf.lfItalic = NULL;
lf.lfUnderline = NULL;
lf.lfStrikeOut = NULL;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = 45;
strcpy(lf.lfFaceName, "隶书");
pUnitFont.CreateFontIndirect(&lf);
pOldFont = (CFont *)dc.SelectObject(&pUnitFont);
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(RGB(0, 0, 0));
CRect rectText(10 + 3, 10 + 3, 450 + 3, 200 + 3);
dc.DrawText("这个是一个测试实例", rectText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
dc.SetTextColor(RGB(255, 0, 0));
rectText = CRect(10, 10, 450, 200);
dc.DrawText("这个是一个测试实例", rectText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
dc.SelectObject(pOldFont);
pUnitFont.DeleteObject();
例子的演示效果可以见下:
我们可以看到下面的例子,这样比直接显示一个简单的文字看起来效果好多了,在这里我们在输出仪表圆盘刻度,仪表显示数据名称和显示实时值的时候采用这种方法增加控件的美观性。
首先在调用刷新的时候由于背景的绘制是保持不变的,不同于指针和实时值的绘制,要根据值的变化动态绘制,所以在调用背景的绘制的时候我们可以进行一次绘制,每次调用刷新背景的时候将内存中的绘制直接贴到界面上。
//绘制仪表背景
void CRoundMeter::DrawMeterBackground(CDC *pDC, CRect &rect)
{
CPen pPenDeep, pPenThin, *pOldPen;
CBrush m_brushBack, pBackBrush, *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();
pPenDeep.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
pDC->SetTextColor(RGB( 0, 0, 0));
pDC->SetBkMode(TRANSPARENT);
m_ptMeterCenter = rect.CenterPoint(); //点中心坐标
int nRadius = 0;
if (rect.Width() <= rect.Height())
{
nRadius = rect.Width() - 5;
}
else
{
nRadius = rect.Height() - 5;
}
m_nRadiusFrame = nRadius;
//绘制仪表圆盘
m_brushBack.CreateSolidBrush(RGB(182, 182, 182));
pOldBrush = (CBrush *)pDC->SelectObject(&m_brushBack);
CRect rectRound(m_ptMeterCenter.x - nRadius / 2,
m_ptMeterCenter.y - nRadius / 2,
m_ptMeterCenter.x + nRadius / 2,
m_ptMeterCenter.y + nRadius / 2);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
rectRound.SetRect(m_ptMeterCenter.x - nRadius / 2 + nRadius / 50,
m_ptMeterCenter.y - nRadius / 2 + nRadius / 50,
m_ptMeterCenter.x + nRadius / 2 - nRadius / 50,
m_ptMeterCenter.y + nRadius / 2 - nRadius / 50);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pPenThin.DeleteObject();
pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
rectRound.SetRect(m_ptMeterCenter.x - nRadius / 2 + nRadius / 16,
m_ptMeterCenter.y - nRadius / 2 + nRadius / 16,
m_ptMeterCenter.x + nRadius / 2 - nRadius / 16,
m_ptMeterCenter.y + nRadius / 2 - nRadius / 16);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
m_brushBack.DeleteObject();
pBackBrush.CreateSolidBrush(RGB( 0, 0, 0));
pOldBrush = pDC->SelectObject(&pBackBrush );
pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
rectRound.SetRect(m_ptMeterCenter.x - nRadius / 15,
m_ptMeterCenter.y - nRadius / 15,
m_ptMeterCenter.x + nRadius / 15,
m_ptMeterCenter.y + nRadius / 15);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
m_brushBack.DeleteObject();
m_brushBack.CreateSolidBrush(m_BackColor);
pOldBrush = pDC->SelectObject(&m_brushBack);
rectRound.SetRect(m_ptMeterCenter.x - nRadius / 15 + nRadius / 50,
m_ptMeterCenter.y - nRadius / 15 + nRadius / 50,
m_ptMeterCenter.x + nRadius / 15 - nRadius / 50,
m_ptMeterCenter.y + nRadius / 15 - nRadius / 50);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
//画刻度
int nTicks = m_nTicks;
int nSubTicks = m_nSubTicks;
char strFigure[MAXNAMELENGTH + 1];
const int nSidePos = 40;
memset(strFigure, 0, sizeof(strFigure));
double dRadius = fabs(nRadius / 2 - nRadius / 16);
double dWidth = fabs(nRadius / 15);
double dMaxAngle = double(300.00f / nTicks); //每个大格的角度
double dMinAnble = dMaxAngle / nSubTicks; //每个小格的角度
pPenDeep.DeleteObject();
pPenDeep.CreatePen(PS_SOLID, 2, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&pPenDeep);
pDC->SetTextColor(RGB( 0, 0, 255));
pDC->SetBkMode(TRANSPARENT);
CFont fDrawFont;
fDrawFont.CreateFont(15, 0, 0, 0, 20, FALSE, FALSE, ANSI_CHARSET,
CLIP_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY,
DEFAULT_PITCH, FF_DONTCARE, "Arail");
CFont *pOldFont = (CFont *)pDC->SelectObject(&fDrawFont);
for (int i=0; i
{
CPoint ptStartTick, ptEndTick;
double dDrawAngle = (i * dMaxAngle + 30) * PI / 180;
ptStartTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle));
ptStartTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle));
ptEndTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle) + dWidth * sin(dDrawAngle));
ptEndTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle) - dWidth * cos(dDrawAngle));
pDC->MoveTo(ptStartTick);
pDC->LineTo(ptEndTick);
sprintf(strFigure, "%.2f", (m_dMaxValue - m_dMinValue) * i / nTicks);
if (dMaxAngle * (nTicks - i) - 30 < 60)
{
pDC->SetTextColor(RGB(255, 255, 255));
pDC->TextOut(ptEndTick.x - nSidePos + 1, ptEndTick.y + 1, strFigure);
pDC->SetTextColor(RGB(0, 0, 255));
pDC->TextOut(ptEndTick.x - nSidePos, ptEndTick.y, strFigure);
}
else if (dMaxAngle * (nTicks - i) - 30 <= 90)
{
pDC->SetTextColor(RGB(255, 255, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 2 + 1, ptEndTick.y + 3 + 1, strFigure);
pDC->SetTextColor(RGB(0, 0, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 2, ptEndTick.y + 3, strFigure);
}
else if (dMaxAngle * (nTicks - i) - 30 < 140)
{
pDC->SetTextColor(RGB(255, 255, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 3 + 1, ptEndTick.y + 1, strFigure);
pDC->SetTextColor(RGB(0, 0, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 3, ptEndTick.y, strFigure);
}
else
{
pDC->SetTextColor(RGB(255, 255, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 10 + 1, ptEndTick.y + 1, strFigure);
pDC->SetTextColor(RGB(0, 0, 255));
pDC->TextOut(ptEndTick.x - nSidePos / 10, ptEndTick.y, strFigure);
}
}
pDC->SelectObject(pOldFont);
fDrawFont.DeleteObject();
pDC->SelectObject(pOldPen);
pPenThin.DeleteObject();
pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
dWidth = fabs(nRadius / 20);
for (i=0; i
{
for (int j=0; j
{
CPoint ptSubStartTick, ptSubEndTick;
double dDrawAngle = ((i * dMaxAngle + 30) + (j * dMinAnble)) * PI / 180;
ptSubStartTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle));
ptSubStartTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle));
ptSubEndTick.x = int(m_ptMeterCenter.x - dRadius * sin(dDrawAngle) + dWidth * sin(dDrawAngle));
ptSubEndTick.y = int(m_ptMeterCenter.y + dRadius * cos(dDrawAngle) - dWidth * cos(dDrawAngle));
pDC->MoveTo(ptSubStartTick);
pDC->LineTo(ptSubEndTick);
}
}
pDC->SelectObject(pOldPen);
//文本格式的初始化
int nRadiusFrame = m_nRadiusFrame / 2;
CFont pUnitFont;
LOGFONT lf;
lf.lfEscapement = 0;
lf.lfItalic = NULL;
lf.lfUnderline = NULL;
lf.lfStrikeOut = NULL;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = nRadiusFrame / 4;
strcpy(lf.lfFaceName, "隶书");
pUnitFont.CreateFontIndirect(&lf);
pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
pDC->SetTextColor(RGB(255, 255, 255));
CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f + 2),
int(m_ptMeterCenter.y + nRadiusFrame * 0.10f + 2),
int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
int(m_ptMeterCenter.y + nRadiusFrame * 0.50f + 2));
pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SetTextColor(RGB(0, 0, 255));
rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
int(m_ptMeterCenter.y + nRadiusFrame * 0.10f),
int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
int(m_ptMeterCenter.y + nRadiusFrame * 0.50f));
pDC->DrawText(m_strUnits, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SelectObject(pOldFont);
pUnitFont.DeleteObject();
pPenDeep.DeleteObject();
pPenThin.DeleteObject();
pBackBrush.DeleteObject();
m_brushBack.DeleteObject();
}
</NTICKS;></NTICKS+1;>
绘制仪表指针:
void CRoundMeter::DrawNeedle(CDC *pDC, CRect &rect)
{
CRect rectRound;
CPen pPenThin, *pOldPen;
CBrush pNeedleBrush, *pOldBrush;
pNeedleBrush.CreateSolidBrush(m_NeedleColor);
pOldBrush = (CBrush *)pDC->SelectObject(&pNeedleBrush);
double dRadius = fabs(m_nRadiusFrame / 2 - m_nRadiusFrame / 16) - fabs(m_nRadiusFrame / 15);
if (m_dCurrentValue < m_dMinValue)
{
m_dCurrentValue = m_dMinValue;
}
else if (m_dCurrentValue > m_dMaxValue)
{
m_dCurrentValue = m_dMaxValue;
}
double dAngle = 300 * (m_dCurrentValue - m_dMinValue) / (m_dMaxValue - m_dMinValue);
CPoint ptRgn[5];
ptRgn[0].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180));
ptRgn[0].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180));
ptRgn[1].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180)
- (m_nRadiusFrame / 20) * sin(dAngle * PI / 180));
ptRgn[1].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180)
+ (m_nRadiusFrame / 25) * cos(dAngle * PI / 180));
ptRgn[2].x = int(m_ptMeterCenter.x - (dRadius - 10) * sin((dAngle + 30) * PI / 180));
ptRgn[2].y = int(m_ptMeterCenter.y + (dRadius - 10) * cos((dAngle + 30) * PI / 180));
ptRgn[3].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180)
- (m_nRadiusFrame / 20) * sin((dAngle + 60) * PI / 180));
ptRgn[3].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180)
+ (m_nRadiusFrame / 25) * cos((dAngle + 60) * PI / 180));
ptRgn[4].x = int(m_ptMeterCenter.x - (m_nRadiusFrame / 25) * sin((dAngle + 30) * PI / 180));
ptRgn[4].y = int(m_ptMeterCenter.y + (m_nRadiusFrame / 25) * cos((dAngle + 30) * PI / 180));
pDC->SetPolyFillMode(WINDING);
pDC->Polygon(ptRgn, 5);
pDC->SelectObject(pOldBrush);
pNeedleBrush.DeleteObject();
pNeedleBrush.CreateSolidBrush(RGB( 0, 0, 0));
pOldBrush = pDC->SelectObject(&pNeedleBrush );
pPenThin.CreatePen(PS_SOLID, 1, RGB( 0, 0, 0));
pOldPen = (CPen *)pDC->SelectObject(&pPenThin);
rectRound.SetRect(m_ptMeterCenter.x - m_nRadiusFrame / 16,
m_ptMeterCenter.y - m_nRadiusFrame / 16,
m_ptMeterCenter.x + m_nRadiusFrame / 16,
m_ptMeterCenter.y + m_nRadiusFrame / 16);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
pNeedleBrush.DeleteObject();
pNeedleBrush.CreateSolidBrush(RGB( 255, 128, 64));
pOldBrush = pDC->SelectObject(&pNeedleBrush);
rectRound.SetRect(m_ptMeterCenter.x - m_nRadiusFrame / 15 + m_nRadiusFrame / 50,
m_ptMeterCenter.y - m_nRadiusFrame / 15 + m_nRadiusFrame / 50,
m_ptMeterCenter.x + m_nRadiusFrame / 15 - m_nRadiusFrame / 50,
m_ptMeterCenter.y + m_nRadiusFrame / 15 - m_nRadiusFrame / 50);
pDC->Ellipse(rectRound);
pDC->SelectObject(pOldBrush);
pNeedleBrush.DeleteObject();
pPenThin.DeleteObject();
}
绘制仪表实时值,同样采用字体叠加的方法增加控件的美观性:
//绘制仪表实时值
void CRoundMeter::DrawValue(CDC *pDC, CRect &rect)
{
char strCurrentValue[10];
memset(strCurrentValue, 0, sizeof(strCurrentValue));
sprintf(strCurrentValue, "%.2f", m_dCurrentValue);
int nRadiusFrame = m_nRadiusFrame / 2;
CFont pUnitFont, *pOldFont;
LOGFONT lf;
lf.lfEscapement = 0;
lf.lfItalic = NULL;
lf.lfUnderline = NULL;
lf.lfStrikeOut = NULL;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = nRadiusFrame / 6;
strcpy(lf.lfFaceName, "Arial");
pUnitFont.CreateFontIndirect(&lf);
pOldFont = (CFont *)pDC->SelectObject(&pUnitFont);
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(255, 255, 255));
CRect rectUnits(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
int(m_ptMeterCenter.y + nRadiusFrame * 0.40f),
int(m_ptMeterCenter.x + nRadiusFrame * 0.30f + 2),
int(m_ptMeterCenter.y + nRadiusFrame * 0.60f + 2));
pDC->DrawText(strCurrentValue, &rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SetTextColor(RGB( 0, 0, 255));
rectUnits.SetRect(int(m_ptMeterCenter.x - nRadiusFrame * 0.30f),
int(m_ptMeterCenter.y + nRadiusFrame * 0.40f),
int(m_ptMeterCenter.x + nRadiusFrame * 0.30f),
int(m_ptMeterCenter.y + nRadiusFrame * 0.60f));
pDC->DrawText(strCurrentValue, rectUnits, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SelectObject(pOldFont);
pUnitFont.DeleteObject();
}
至此控件的绘制工作已经基本完成。在这里我们考虑一个新的功能,就是在很多工业控制联合外围设备的时候有很多需要对外围设备制定一个值下发到外围设备从而达到精确控制的作用。我们指定某个值的方法有很多,比如采用输入编辑控件的方法去实现,加入我们的这个指定的值正是我们这个仪表显示的,我们在这里增加一个鼠标拖动仪表指针角度从而得到实时值的方法来代替编辑框的方法,能更形象的标示功能的操作要求。所以我们要在控件中增加WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP消息来处理鼠标与圆盘角度实时值之间的关系,同时进行仪表的重绘工作。
首先封装鼠标当前的位置获得圆盘仪表的指针的角度
double CRoundMeter::GetAngle(CPoint &ptCur)
{
double dAngle = 0;
double xDisc = ptCur.x - m_ptMeterCenter.x;
double yDisc = ptCur.y - m_ptMeterCenter.y;
double tmpVal = yDisc / xDisc;
if (xDisc < 0 && yDisc > 0)
{
if ((-atan(tmpVal) * 180 / PI) > 60)
{
dAngle = 0;
}
else
{
dAngle = 60 + atan(tmpVal) * 180 / PI;
}
}
else if (xDisc < 0 && yDisc <= 0)
{
dAngle = 60 + atan(tmpVal) * 180 / PI;
}
else if (xDisc > 0 && yDisc <= 0)
{
dAngle = 240 + atan(tmpVal) * 180 / PI;
}
else if (xDisc > 0 && yDisc > 0)
{
if ((-atan(tmpVal) * 180 / PI) > 60)
{
dAngle = 300;
}
else
{
dAngle = 240 + atan(tmpVal) * 180 / PI;
}
}
else
{
dAngle = 150;
}
return dAngle;
}
然后通过这个角度获得圆盘的实时值:
double CRoundMeter::GetValue(double &dAngle)
{
double dCurrentValue;
dCurrentValue = m_dMinValue + dAngle * (m_dMaxValue - m_dMinValue) / 300;
return dCurrentValue;
}
然后我们就可以在鼠标触发的消息中进行随意控制了。
void CRoundMeter::OnLButtonDown(UINT nFlags, CPoint point)
{
if (!m_bMouseDrag)
{
return;
}
SetCapture();
double dAngle = GetAngle(point);
m_dCurrentValue = GetValue(dAngle);
Invalidate();
CStatic::OnLButtonDown(nFlags, point);
}
void CRoundMeter::OnMouseMove(UINT nFlags, CPoint point)
{
if(nFlags & MK_LBUTTON & m_bMouseDrag)
{
double dAngle = GetAngle(point);
m_dCurrentValue = GetValue(dAngle);
Invalidate();
}
CStatic::OnMouseMove(nFlags, point);
}
void CRoundMeter::OnLButtonUp(UINT nFlags, CPoint point)
{
ReleaseCapture();
CStatic::OnLButtonUp(nFlags, point);
}
编译运行,然后利用测试工程测试我们的控件,就可以看出效果了。