介绍
在中小型电力行业系统中,曲线的要求比较严格,虽然网络上面各种曲线控件非常多,但是很多只能提供一段时间的曲线显示或者历史若干个点的显示。比如在一个普通的小型水电站系统中我们会牵涉到许多的点(可能甚至达到400到500多个),一般的电站运行人员或者管理者希望能够对这类点的历史运行状况进行还原(即能够将历史运行的各个点状况进行还原),曲线就是非常重要的一个环节了,考虑到各种因素,由于很多控件虽然外表包装漂亮,但是功能觉得不能这么完善,所以我自己写了一个显示控件,现进行简要的介绍,希望对这个方面寻求帮助的人有帮助!
正文
程序运行情况如下:
采用HOVEREDIT的READONLY形式显示点各种信息,采用网络上面下载的DIGITAL控件进行当前值的显示。在曲线图上面移动鼠标,当前值准确显示当前点的实际值。
以下代码在VIEW类中实现,封装工作没有进行,如果各位有兴趣封装可以发送一份代码给我。^_^
曲线实现思路很简单,即在背景图上面进行点的绘制就可以了,我们以点的形式来描述实现情况。因为在电力系统中我们习惯将各个数据称呼为点,比如某某线路A向电流我们可以称之为点1,我们这里对该点进行定义一个结构体描述
struct ItemMsg{
CString szId;
CString szName; //设备名称标示
float fHisVal[720]; //历史记录数据,用于曲线记录
};
定义全天显示720个点,其中720个点计算方法如下:如果我们采用每2分钟保存一个点的话,那么一天24小时计算为:2×30×24 = 720,对于要求精度比较高的地方,精度集中到几个秒记录一次的话,这里可以根据实际情况进行更改。在我这次的现场实现中720个点已经能够满足要求了,即在屏幕显示区域占用720个象素点,每个象素点描述信息为2分钟的值。如果采用1分钟一个点就需要1440个点了。由于我们常用的显示器为17‘,分辨率基本上为:1024X768,即X方向最多1024个象素点,这时的1440已经超过了1024了,所以我们需要程序代码加载方法进行曲线的扩充(比如动态加载SCROLLBAR进行曲线的扩充和收缩)。那种情况我们暂时不予考虑。^_^
由于我这次数据库采用点组态功能,将点分为电气(各种外围厂家数据点)和模拟(PLC上来数据点)两个部分,程序开始我们需要对个点进行内容描述。因为这个描述必须与存储历史数据保持一致。
void CCurverDemoView::InitbufferVal()
{
this->m_ItemMsg = new ItemMsg[1000];
CRecordset res(&db);
CString sql;
CDBVariant var;
var.Clear();
CString szId;
int nCount = 0;
sql.Format("Select * From Realanaquantity");
res.Open(CRecordset::forwardOnly,sql,CRecordset::readOnly);
while(!res.IsEOF())
{
CString szName;
res.GetFieldValue("name",var);
szName = *var.m_pstring;
if(szName != "undefine")
{
res.GetFieldValue("ID",var);
szId = *var.m_pstring;
m_ItemMsg[nCount].szId = szId;
m_ItemMsg[nCount].szName = szName;
nCount++;
}
res.MoveNext();
}
res.Close();
///
sql.Format("Select * From Realelecquantity");
res.Open(CRecordset::forwardOnly,sql,CRecordset::readOnly);
while(!res.IsEOF())
{
CString szName;
res.GetFieldValue("name",var);
szName = *var.m_pstring;
if(szName != "undefine")
{
res.GetFieldValue("ID",var);
szId = *var.m_pstring;
m_ItemMsg[nCount].szId = szId;
m_ItemMsg[nCount].szName = szName;
nCount++;
}
res.MoveNext();
}
res.Close();
nTolCount = nCount;
for(int i=0;i<this->nTolCount;i++)
for(int j=0;j<720;j++)
this->m_ItemMsg[i].fHisVal[j] = float(GetRandom(40000,50000))/100;
}
加载背景位图
void CCurverDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect rect;
GetClientRect(&rect);
HBITMAP hbitmap;
switch(this->m_SysType)
{
case CURVE:
hbitmap=::LoadBitmap(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_ONTIME_CURVE));
break;
case ABOUT:
hbitmap=::LoadBitmap(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_BACKBMP));
break;
default:
hbitmap=::LoadBitmap(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_ONTIME_CURVE));
break;
}
HDC hMenDC=::CreateCompatibleDC(NULL);
SelectObject(hMenDC,hbitmap);
::StretchBlt(dc.m_hDC,0,0,1024,768,hMenDC,0,0,1024,768,SRCCOPY);
::DeleteDC(hMenDC);
::DeleteObject(hbitmap);
DrawRealCurve();
// Do not call CView::OnPaint() for painting messages
}
定义曲线绘制边界和显示区域
#define GRAPH_LEFT 109 //曲线图位置
#define GRAPH_TOP 143-45 //曲线图位置
#define GRAPH_RIGHT 935 //曲线图位置
#define GRAPH_BOTTOM 625-45 //曲线图位置
#define GRAPH_LEFT_MARGIN 69 //曲线图左边距
#define GRAPH_RIGHT_MARGIN 37 //曲线图右边距
#define GRAPH_TOP_MARGIN 30 //曲线图顶边距
#define GRAPH_BOTTOM_MARGIN 30 //曲线图底边距
绘制实时曲线:
void CCurverDemoView::DrawRealCurve()
{
CDC *pDC = GetDC();
CPen pNewPen,*pOldPen;
pNewPen.CreatePen(0,1,RGB(0,255,0));
pOldPen = pDC->SelectObject(&pNewPen);
int nXPoint[720],nYPoint[720];
float fMaxNum = 0.00f;
for(int i=0;i<720;i++)
{
if(m_ItemMsg[this->nPointSize].fHisVal[i] > fMaxNum)
fMaxNum = m_ItemMsg[this->nPointSize].fHisVal[i];
}
/////显示参数
CString szMaxNum;
CSize cSize;
fMaxNum = fMaxNum*3/2;
if(fMaxNum == 0) fMaxNum = 1;
CPen pNewPen1,*pOldPen1;
pNewPen1.CreatePen(PS_SOLID,1,RGB(255,0,0));
pOldPen1 = (CPen *)pDC->SelectObject(&pNewPen1);
pDC->SetBkColor(RGB(0,0,0));
pDC->SetTextColor(RGB(255,0,0));
szMaxNum.Format("%.2f",fMaxNum);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN,szMaxNum);
szMaxNum.Format("%.2f",fMaxNum*4/5);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN+90,szMaxNum);
szMaxNum.Format("%.2f",fMaxNum*3/5);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN+180,szMaxNum);
szMaxNum.Format("%.2f",fMaxNum*2/5);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN+270,szMaxNum);
szMaxNum.Format("%.2f",fMaxNum/5);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN+360,szMaxNum);
/*szMaxNum.Format("%.2f",0);
cSize = pDC->GetTextExtent(szMaxNum);
pDC->TextOut(GRAPH_LEFT+GRAPH_LEFT_MARGIN-cSize.cx-10,GRAPH_TOP+GRAPH_TOP_MARGIN+410,szMaxNum);
*/ pDC->SelectObject(pOldPen1);
pNewPen1.DeleteObject();
////////////
float nMinNum = float(30*14)/fMaxNum;
for(i=0;i<720;i++)
{
nYPoint[i]=GRAPH_BOTTOM-GRAPH_BOTTOM_MARGIN-int((m_ItemMsg[this->nPointSize].fHisVal[i])*nMinNum);
if(int((m_ItemMsg[this->nPointSize].fHisVal[i])*nMinNum) > 420)
nYPoint[i]=GRAPH_TOP+GRAPH_TOP_MARGIN;
if(int((m_ItemMsg[this->nPointSize].fHisVal[i])*nMinNum) == 0)
nYPoint[i]=GRAPH_TOP+GRAPH_TOP_MARGIN+420;
nXPoint[i]=GRAPH_LEFT+GRAPH_LEFT_MARGIN+i;
if(i==0)
pDC->MoveTo(nXPoint[i],nYPoint[i]);
else
pDC->LineTo(nXPoint[i],nYPoint[i]);
}
pDC->SelectObject(&pOldPen);
pNewPen.DeleteObject();
ReleaseDC(pDC);
}
程序调用:
void CCurverDemoView::OnMsgSel()
{
CMsgSelDlg dlg;
CString szMsg,szDate;
if(dlg.DoModal() == IDOK)
{
if(ReadHisMsgFromFile(dlg.szYear,dlg.szMonth,dlg.szDay))
{
szMsg = dlg.szName;
szDate = dlg.szYear+"年"+dlg.szMonth+"月"+dlg.szDay+"日";
this->m_LineMsgEdit.SetWindowText(szMsg);
this->m_LineDataEdit.SetWindowText(szDate);
for(int i = 0; i < 1000; i++)
{
if(m_ItemMsg[i].szId == dlg.szId)
{
this->nPointSize = i;
break;
}
}
Invalidate();
}
}
}
处理鼠标移动进行动态显示数据:
void CCurverDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
int i = point.x - (GRAPH_LEFT+GRAPH_LEFT_MARGIN);
CString szVal;
if(i>=0 && i < 720)
{
szVal.Format("%.2f",this->m_ItemMsg[this->nPointSize].fHisVal[i]);
this->m_EditMsg.SetText(szVal);
}
CView::OnMouseMove(nFlags, point);
}
/////////////
有数据的读取当然必须配合数据的定时存储,下面为数据存储方法:
void CDataSaveWnd::SaveItemVal()
{
CString sPath,szFileName;
CTime t = CTime::GetCurrentTime();
sPath.Format("d:\\历史数据文件夹\\%.4d年%.2d月%.2d日",
t.GetYear(),t.GetMonth(),t.GetDay());
szFileName.Format(sPath+"\\Device.qigao");
//read file data
CFile file;
float *pfValue=new float[this->nTolCount+1];
for(int i = 0; i<this->nTolCount+1; i++)
{
if(i == 0)
pfValue[i] = float(t.GetHour()*30+t.GetMinute()/2);
else
pfValue[i] = fVal[i-1];
}
::CreateDirectory(sPath,NULL);
file.Open(szFileName,CFile::modeCreate|CFile::modeReadWrite|CFile::modeNoTruncate);
file.SeekToEnd();
file.Write(pfValue,sizeof(float)*(this->nTolCount+1));
file.Close();
if(pfValue)
delete pfValue;
}
void CDataSaveWnd::ReadItemVal()
{
CRecordset res(&db);
CString sql;
CDBVariant var;
var.Clear();
CString szId;
int nCount = 0;
sql.Format("Select * From Realanaquantity");
res.Open(CRecordset::forwardOnly,sql,CRecordset::readOnly);
while(!res.IsEOF())
{
CString szName;
res.GetFieldValue("name",var);
szName = *var.m_pstring;
if(szName != "undefine")
{
res.GetFieldValue("RealValue",var);
fVal[nCount] = (float)atof(*var.m_pstring);
nCount++;
}
res.MoveNext();
}
res.Close();
///
sql.Format("Select * From Realelecquantity");
res.Open(CRecordset::forwardOnly,sql,CRecordset::readOnly);
while(!res.IsEOF())
{
CString szName;
res.GetFieldValue("name",var);
szName = *var.m_pstring;
if(szName != "undefine")
{
res.GetFieldValue("RealValue",var);
fVal[nCount] = (float)atof(*var.m_pstring);
nCount++;
}
res.MoveNext();
}
res.Close();
nTolCount = nCount;
}
详细方法可以见示例工程。
由于文章添加时间比较仓促,中间可能有很多不详细的地方,欢迎各位与我交流意见:13975102873@hnmcc.com
正文完
文章来源于领测软件测试网 https://www.ltesting.net/