介紹
在工業控制系統開發過程中,圖形顯示方面佔有著很重要的作用。比起很多專用的組態軟體,他們有著強大的在圖形系統,能夠組態出來非常漂亮的系統。現在的很多的工業圖形開發包都需要支付費用,很多漂亮的控制項比如儀錶等只能看圖興歎了。前些天一個朋友做一個泵站的監控系統,由於缺少相關的控制項,在研究了該類控制項的編程方法上,借鑒網路上的一些編程資料,完成了一些可用於工業控制系統開發使用的控制項。
本文
國內現在的工業組態軟體很多都是採用組態軟體,比如組態王,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();
}