介绍
2004年下半年我被公司派往刚果完成中国援外项目中间水电、变电、调度系统开发任务,由于开发系统中一般公司采用组态王等组态软件完成,这类组态软件虽然在界面表现形式方面比较丰富,但是由于考虑到系统得运行效率和功能的扩张,我采用VC自己编写上位机软件程序的方法实现。其中一个人完成上位机监控系统软件,通讯部分程序,调度自动化部分程序(遥控、遥信、遥测),由于项目没有全部验收,今年好要过去,但是这次开发中对我软件开发能力有了很大的提高,其中一些的处理过程和技术实现我特写出来,希望对致力于电力监控系统开发和寻求相关系统开发的人有帮助。由于篇幅限制,特讲项目分块发布。
正文
选择VC开发监控系统很大一个方面是C++语言对外围硬件的支持和实时性方面的原因。由于用国内众多组态软件大都存在加密的问题(大部分采用点计算费用的形式),这样如果我们针对每个项目采用组态软件完成的话将是一笔额外的费用,其实这些工作我们都可以用VC编译器轻松的完成,虽然在界面的表达方面不能和组态软件相比,但是这对于一些致力于中小型自动化公司开发电站监控项目的一个不错的选择。
电力监控系统我一般分为上位机软件部分和通讯部分。上位机软件部分一般处理的事情包括:数据的刷新显示、历史数据的形成和统计、报表的自动生成和召唤打印、曲线的生成和召唤、某些特殊的权限操作和参数设置等。一般的组态软件在数据的刷新显示方面的形式比较丰富,他们能够模拟现场实物运行情况直观进行显示,比如显示某个点温度数据,他们能够模拟一个温度计进行显示。在VC系统开发中,我们也可以简单实现这种功能(此类控件网络上面很多都提供了免费源代码),同时我们可以直接用一种TextOut的方法实现出来。电力监控系统界面部分数据显示一般需要主接线图、计量汇总界面、测量汇总界面、个数字量节点运行界面和各种功能显示界面组成,显示内容为我们通过串口或者TCP/IP传递过来的信号点数据。在监控系统中我们可以采用定时器驱动的方法实现。界面程序采用定时的读取这些数据点的当前回送状态并进行数据的刷新工作。在刚果系统中我采用1S的形式实现,定时1S能够基本保证实时性不是很高的监控系统的监控要求,同时定时1S能够保证在整天、整点、整时、整分进行。因为监控系统需要在整天或者整点或者整分的时候进行实时数据保存成为历史数据的工作,同时还要进行历史数据的统计规类等工作。如果我们要求实时精度比较高的话,我们可以采用线程刷新的方式实现。即通讯程序采集数据完成后刷新数据显示线程通过等待Event进行刷新工作了,只是这样实现起来麻烦一点,呵呵。显示刷新原来我采用的是双缓冲的方法调用背景位图,然后在位图上面进行图形界面的编程
好了,言归正题了,这次讲的是如何进行系统整体框架设计的吧。既然没有采用组态软件开发,系统的整体框架总要进行某些特殊的处理吧,操作界面是每个人首先接触到的第一眼的东西,当然,如果我们就采用VC形成的基本框架好像不大好看哦。我找了一些免费的换肤控件比如ActiveSkin,SkinMagic,BCG,AppFace等,后来发现AppFace处理还可以,所以我采用了AppFace进行了界面的换肤处理。这次用到了很多的网络上面的控件,包含vckbase,codeproject等网站提供的免费控件,我个人认为一个优秀的程序员不是一个很厉害的书写代码的人,更应该是能够运行现成的东西完成开发效率的人(可能很多人有不同的意见吧)。下面贴几个程序运行模拟界面吧
主接线图部分:
测量汇总部分:
计量汇总部分:
曲线显示部分:
系统帮助信息部分:
程序对话框模式:
框架部分实现如下:
设置程序皮肤:将AppFace.dll,BluePinna.urf,GtBase.urf,GtClassic.urf,AppFace.h拷贝到程序目录下,
然后加入下面内容
CAppFace m_MainFace;//定义控件对象变量
InitInstance()程序入口添加下面换肤内容
char path[256];
GetModuleFileName(NULL,path,256) ;
char * p = strrchr(path,@#\\@#) ;
if(p) strcpy(p,"\\GtBase.urf\0\0");
m_MainFace.Start(path) ;
ExitInstance()程序退出清理工作:
this->m_MainFace.Remove();
其他一些程序框架的处理:
监控系统要求能够开机自动运行程序,因为系统操作对象可能是一个对电脑所知无几的人
void CMainFrame::EnableAutoStart()//设置程序自动开机运行 { CString sPath; int nPos; HKEY RegKey; GetModuleFileName(NULL,sPath.GetBufferSetLength(MAX_PATH+1),MAX_PATH); sPath.ReleaseBuffer(); nPos=sPath.ReverseFind(@#\\@#); sPath=sPath.Left(nPos); CString lpszFile=sPath+"\\YbkDemo.exe"; CFileFind fFind; BOOL bSuclearcase/" target="_blank" >ccess; bSuccess=fFind.FindFile(lpszFile); fFind.Close(); if(bSuccess) { CString fullName; fullName=lpszFile; RegKey=NULL; RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&RegKey); RegSetValueEx(RegKey,"彭水白云监控系统",0,REG_SZ,(const unsigned char*)(LPCTSTR)fullName,fullName.GetLength()); this->UpdateData(FALSE); } else { ::AfxMessageBox("没找到执行程序,自动运行失败"); exit(0); } } void CMainFrame::SetStartMode()//设置显示器分辨率 { DEVMODE lpDevMode; lpDevMode.dmPelsHeight=768; lpDevMode.dmPelsWidth=1024; lpDevMode.dmDisplayFrequency=85; lpDevMode.dmFields=DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY; ChangeDisplaySettings(&lpDevMode,0); this->BringWindowToTop(); } |
触发WM_NCLBUTTONDOWN,截获鼠标双击标题栏进行窗体的还原和最大化操作
void CMainFrame::OnNcLButtonDblClk(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if(nFlags != HTCAPTION) CFrameWnd::OnLButtonDblClk(nFlags, point); } |
触发WM_SIZE和WM_SIZING消息,截获用户鼠标对窗体的大小修改操作
void CMainFrame::OnSize(UINT nType, int cx, int cy) { CFrameWnd::OnSize(nType, cx, cy); // TODO: Add your message handler code here if(this->GetSafeHwnd()) MoveWindow(CRect(0,0,::GetSystemMetrics(SM_CXSCREEN), ::GetSystemMetrics(SM_CYSCREEN)),TRUE); } void CMainFrame::OnSizing(UINT fwSide, LPRECT pRect) { CFrameWnd::OnSizing(fwSide, pRect); // TODO: Add your message handler code here if(this->GetSafeHwnd()) MoveWindow(CRect(0,0,::GetSystemMetrics(SM_CXSCREEN), ::GetSystemMetrics(SM_CYSCREEN)),TRUE); } //================================= // 创建自定义工具条,这个方法来源于VCKBASE //================================= void CMainFrame::CreateToolBar() { m_wndToolBar.CreateEx(this,TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED); //创建图形列表 CImageList img; img.Create(IDB_COMMCOLD, 21, 0, RGB(255, 0, 255)); m_wndToolBar.GetToolBarCtrl().SetImageList(&img); img.Detach(); img.Create(IDB_COMMHOT, 21, 0, RGB(255, 0, 255)); m_wndToolBar.GetToolBarCtrl().SetHotImageList(&img); img.Detach(); //设置工具条按钮宽度最小、最大值 m_wndToolBar.GetToolBarCtrl().SetButtonWidth(70, 100); m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT | TBSTYLE_TRANSPARENT); m_wndToolBar.SetButtons(NULL, 8);//按钮个数 // 创建每个按钮 CString str; str="主接线图"; m_wndToolBar.SetButtonInfo(0, ID_ZHUJIEXIAN, TBSTYLE_BUTTON, 0); m_wndToolBar.SetButtonText(0, str); m_wndToolBar.SetButtonInfo(1, ID_JILIANG, TBSTYLE_BUTTON, 1); str="计量界面"; m_wndToolBar.SetButtonText(1, str); m_wndToolBar.SetButtonInfo(2, ID_CELIANG, TBSTYLE_BUTTON, 2); str="测量界面"; m_wndToolBar.SetButtonText(2, str); m_wndToolBar.SetButtonInfo(3, ID_SEEK_DINGZHI, TBSTYLE_BUTTON, 3); str="定值查询"; m_wndToolBar.SetButtonText(3, str); m_wndToolBar.SetButtonInfo(4, ID_TIME_LINESEL, TBSTYLE_BUTTON, 4); str="实时曲线"; m_wndToolBar.SetButtonText(4, str); m_wndToolBar.SetButtonInfo(5, ID_HIS_LINESEL, TBSTYLE_BUTTON, 5); str="历史曲线"; m_wndToolBar.SetButtonText(5, str); m_wndToolBar.SetButtonInfo(6, ID_HELPTOPIC, TBSTYLE_BUTTON, 6); str="系统信息"; m_wndToolBar.SetButtonText(6, str); m_wndToolBar.SetButtonInfo(7, ID_COMM_EXIT, TBSTYLE_BUTTON, 7); str="系统退出"; m_wndToolBar.SetButtonText(7, str); CRect rectToolBar; //获取工具条按钮大小 m_wndToolBar.GetItemRect(0, &rectToolBar); //设置工具条的按钮大小和图标大小 m_wndToolBar.SetSizes(rectToolBar.Size(), CSize(21,22)); } |
下面是窗体创建函数
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; //自己创建富有个性的工具条 CreateToolBar(); if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don@#t want the toolbar to // be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); this->m_wndStatusBar.SetPaneInfo(1,0,1,300); EnableAutoStart();//设置程序自动开机运行 SetStartMode();//设置显示器分辨率 FindWindow("Shell_TrayWnd",NULL)->ShowWindow(SW_HIDE);//隐藏任务栏 //获得窗口风格 LONG style = ::GetWindowLong(m_hWnd,GWL_STYLE); //设置新的风格 style &= ~(WS_MINIMIZEBOX); style &= ~(WS_MAXIMIZEBOX); ::SetWindowLong(m_hWnd,GWL_STYLE,style); //重化窗口边框 CRect rc; GetWindowRect(&rc); ::SetWindowPos(m_hWnd,HWND_NOTOPMOST,rc.left,rc.top,rc.Width(),rc.Height(),SWP_DRAWFRAME); return 0; } |
触发WM_CLOSE,进行程序退出时的清理工作,并加上退出的特效效果
void CMainFrame::OnClose() { // TODO: Add your message handler code here and/or call default CWindowAnima wa(this); wa.Scatter6(50,10); FindWindow("Shell_TrayWnd",NULL)->ShowWindow(SW_SHOW);//隐藏任务栏 CFrameWnd::OnClose(); } |
触发VIEW的WM_PAINT消息,进行背景位图的显示
void CYbkDemoView::OnPaint() { CPaintDC dc(this); // device context for painting CRect rect; GetClientRect(&rect); HBITMAP hbitmap; hbitmap=::LoadBitmap(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_MAIN)); 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); } |
详细代码书写请看我作的示范工程,由于时间匆忙,中间有很多的语句表达可能不是很清楚,您可以在下面的留言中提出来或者,如果有什么问题的话青与我联系:13975102873@hnmcc.com
正文完