最近接手了一个项目,其中涉及到MFC和实时曲线显示的问题,由于我之前从未接触过此类技术,现学现搞,把其间用到的觉得对初学者有用的东西,总结一下。
尤其是关于TeeChart控件部分,网上资料零碎,且很多不全面,代码难以使用。我苦寻数周在外国一些网站上寻到了一些有用的信息,把相关的可运行的代码示例贴在文中,希望能帮到后来者。
MFC部分:
一、
分割窗体
新建一个单文档的MFC工程(注意在向导中设置窗口最大化和分割窗口支持)。
新建两个对话框,用于分割窗口
【注意】对话框的样式(Style)属性改为下层(Child),边框(Border)属性改为None,最开始没有改这个,程序运行的时候报错了。
【注意】将两个对话框生成从CFormView派生的类。
在CMainFrame的OnCreateClient中添加
【例1】把框架分割成两列,右边的一列和对话框绑定。
m_SplitterWnd.CreateStatic(this,1,2)); //把此框架窗口分割成1行2列。 m_SplitterWnd.SetColumnInfo(0, 200, 0) ; //设置第0列的最大宽度为200,最小宽度为0 (此句话非常重要) CRect rect; GetClientRect(&rect); //第1行第1列的窗口与CMyView绑定。其宽度为框架宽度的3/4.高度与框架的高度一致 if(!m_SplitterWnd.CreateView(0,0,RUNTIME_CLASS(CMyView),CSize(rect.Width()/4*3,rect.Height()),pContext)|| //第1行第2列的窗口与我们的对话框CMyDlg绑定。其宽度为框架宽度的1/4. !m_SplitterWnd.CreateView(0,1,RUNTIME_CLASS(CMyDlg), CSize(rect.Width()/4,rect.Height()),pContext)) { return FALSE; } return TRUE;
【例2】在分割后的子窗口上继续分割
在CMainFrame中添加两个成员变量,类型为CSplitterWnd,如下所示
CSplitterWnd m_splitterWnd1;
CSplitterWnd m_splitterWnd2;
添加虚函数virtualBOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext);
程序代码修改部分如下:
BOOLCMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { //创建一个静态分栏窗口,分为一行二列 if(m_splitterWnd1.CreateStatic(this,1,2)==NULL) return FALSE; //设置分割窗口的大小*** m_splitterWnd1.SetColumnInfo(0, 200, 0) ; //设置第0列的最大宽度为200,最小宽度为0 //将CCSplitterWndView连接到0行0列窗格上 m_splitterWnd1.CreateView(0,0,RUNTIME_CLASS(CsplitterwndView),CSize(600,500),pContext); //将第0行1列再分开2行1列 if(m_splitterWnd2.CreateStatic(&m_splitterWnd1,2,1,WS_CHILD|WS_VISIBLE, m_splitterWnd1.IdFromRowCol(0, 1))==NULL) return FALSE; //将FormView1类连接到第二个分栏对象的0行0列 m_splitterWnd2.CreateView(0,0,RUNTIME_CLASS(CForm1),CSize(0,300),pContext);//因为是上下分割,故系统不关注宽度,只看高度,故宽度可以为0 //将FormView2类连接到第二个分栏对象的1行0列 m_splitterWnd2.CreateView(1,0,RUNTIME_CLASS(CForm2),CSize(0,0),pContext); //此高度为0,意为分割后剩下的高度 就是它的了。 return TRUE; } //CsplitterwndView、CForm1、CForm2都是我们自定义的类,可以把他们换成对话框或表单等。
//初始左右分割框架,要调用函数SetColumnInfo来设定分割线位置
对分割出来的一列再进行分割,则是由CreateView中CSize的高度来确定分割线位置
*总结:
* 给框架窗口添加静态拆分视图的过程如下:
* 1. 给框架窗口类添加一个CsplitterWnd数据成员。
* 2. 覆盖框架窗口的OnCreateClient函数,并调用CsplitterWnd::CreateStatic来创建静态拆分视图。
* 3. 使用CsplitterWnd::CreateView在每个静态拆分窗口的窗格中创建视图
* 使用静态拆分窗口的一个优点是由于您自己给窗格添加视图,所以可以控制放入视图的种类
二、
添加自定义消息响应
1、在Resource.h中添加
#define WM_MY_MESSAGE (WM_USER+100)
2、在CMyView的定义中添加: //CMyView是要响应自定义消息的我们的视图类
//{{AFX_MSG(CMyView)
afx_msg LRESULT OnMyMsg(WPARAM, LPARAM) ;
DECLARE_MESSAGE_MAP()
//}}AFX_MSG
3、在CMyView的实现cpp文件中添加
BEGIN_MESSAGE_MAP(CMyView, CFormView)
//{{AFX_MSG_MAP(CMyView)
ON_MESSAGE(WM_MY_MESSAGE, OnMyMsg) //添加消息映射
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
4、实现消息映射函数LRESULT CMyView::OnMyMsg(WPARAM wParam, LPARAM lParam)
发送消息,触发消息响应函数
((ViewPoints*)p)->pMyView->PostMessage(WM_MY_MESSAGE,0, 0);
//CTSDoc * pDoc = (CTSDoc*)(((ViewPoints*)p)->pTeeFFTSIGView->GetDocument()) ;
//pDoc->clientData = _T(buff) ;
TeeChart部分(以VC++6.0 TeeChart8.0为例)
至于如何获取TeeChart控件,如何注册控件,请百度之,网上有很多。
零
在相应的源文件中添加TeeChart的头文件 (有需要的自己再添加)
#include "tchart.h"
#include "series.h"
#include "valuelist.h"
#include "axes.h"
#include "axis.h"
#include "pen.h"
#include "axislabels.h"
#include "teefont.h"
#include "axistitle.h"
#include "aspect.h"
#include "fastlineseries.h"
#include "titles.h"
#include "fastlineseries.h"
#include "panel.h"
#include "legend.h"
#include "annotationtool.h"
#include "page.h"
#include "strings.h"
#include "gradient.h"
#include "IsoSurfaceSeries.h"
一、
在视图类中动态添加TeeChart控件。(解决手工拖动添加控件,编译报”Debug Assertion Failed” 错的问题)
我们添加对话框资源让其继承自CFromView。首先手工静态把控件拖到对话框上,然后建立类向导,生成一个对象m_chart。
在主框架CMainFrame::OnCreateClient()或OnCreate()中【看在哪个函数中分割窗口 产生视图】
RecalcLayout(); //这一句很重要,没有它,会报错。
pView->OnInitialUpdate(); //pView是我们分割窗口得到的CMyDlgView视图的指针。
在视图类CMyDlgView中添加OnInitialUpdate()函数
CRect rect;
GetClientRect(&rect);
m_chart.MoveWindow(&rect, TRUE);
在视图类CMyDlgView中添加WM_CREATE消息响应函数OnCreate()在其中添加
m_chart.Create("",WS_VISIBLE, CRect(0, 0, 0, 0), this, 1234) ;//动态生成控件
m_chart.AddSeries(0);//操作控件
m_chart.Series(0).FillSampleValues(50);
//m_chart是我们的控件TeeChart
即可。
//但此为动态添加的控件,所有设置操作都得通过代码操作。
二、
绘制2D曲线
这个在网上有很多资料了。我在这里再简单总结一下其过程。
A、初始化部分:
在TeeChart控件所在的视图类的OnCreate函数中,进行TeeChart控件的初始化工作。
m_chart.Create("",WS_VISIBLE, CRect(0, 0, 0, 0), this, 1234) ; //动态创建TeeChart控件 m_chart.GetLegend().SetVisible(false);//隐藏图例 m_chart.GetAspect().SetView3D(FALSE); //取消3D显示 //设置图标标题 m_chart.GetHeader().GetText().SetItem(0,COleVariant("传感器实时数据曲线")); //设置纵轴标题 m_chart.GetAxis().GetLeft().GetTitle().SetCaption("数值"); //设置渐变背景 m_chart.GetPanel().GetGradient().SetVisible(true); m_chart.GetPanel().GetGradient().SetStartColor(RGB(192,192,192)); m_chart.GetPanel().GetGradient().SetEndColor(RGB(255,255,255)); //添加曲线 m_chart.AddSeries(0); //设置曲线属性 m_chart.Series(0).SetColor(RGB(255,0,0));//颜色 m_chart.Series(0).GetAsLine().GetLinePen().SetWidth(2); //线型宽度 //设置x轴的取值范围 m_chart.GetAxis().GetBottom().SetMinMax(0,100); //设置x轴上值的格式 m_chart.GetAxis().GetBottom().GetLabels().SetValueFormat("0.0");
B、绘制部分:
在TeeChart控件所在的视图类的自定义消息响应函数OnMyMsg中,或是在定时器中,添加:
COleDateTimeCurTime = COleDateTime::GetCurrentTime(); COleDateTimeSpantmSpan = COleDateTimeSpan(0,0,1,0); //1s CStringcsTime ; csTime= CurTime.Format("%H:%M:%S"); //获取当前时间 //在CMyView中画曲线 m_chart.Series(0).Add(yVal, csTime,RGB(255,0,0)); //第一个参数是y轴值,第二个参数是对应的x轴的标签值(此为当前时间字符串),第三个参数是所绘点的颜色。 CurTime+= tmSpan; m_chart.Series(0).RefreshSeries(); if(m_chart.Series(0).GetCount() > 100) { m_chart.GetAxis().GetBottom().Scroll(1.0,true); //x坐标轴一次移动1格 }
由于TeeChart绘制曲线点的函数Add,每调用一次才绘制一次,故需要有外部消息激发消息响应函数,才能把曲线动态绘制出来。
可以用设置定时器和自定义消息响应函数的方式来实现。(定时器比较简单,消息响应函数上面MFC部分已经讲过)
三、
绘制3D曲线
解决TeeChart8中绘制3D图形报”Invalid class typecast” 错的问题。
A、在承载TeeChart的对话框类Dlg的类定义中,添加:VARIANT SeriesIndex;
B、在类的相关方法中绘制,添加代码:
m_chart.RemoveAllSeries();
//下面的设置很重要(没有的话,会出错)
SeriesIndex.vt=VT_INT;
SeriesIndex.intVal=m_chart.AddSeries(scWaterfall);//scWaterfall=33瀑布图的编号
m_chart.Series(0).GetAsWaterfall().SetIrregularGrid(true);
m_chart.Series(0).GetAsWaterfall().AddXYZ(x,y, z, NULL, RGB(255,0,0));
(TeeChart的3D图有很多种,上面是以瀑布图为例的,其他图种的编号如下:)
const unsigned long scLine = 0;
const unsigned long scBar = 1;
const unsigned long scHorizBar = 2;
const unsigned long scArea = 3;
const unsigned long scPoint = 4;
const unsigned long scPie = 5;
const unsigned long scFastLine = 6;
const unsigned long scShape = 7;
const unsigned long scGantt = 8;
const unsigned long scBubble = 9;
const unsigned long scArrow = 10;
const unsigned long scCandle = 11;
const unsigned long scPolar = 12;
const unsigned long scSurface = 13;
const unsigned long scVolume = 14;
const unsigned long scErrorBar = 15;
const unsigned long scBezier = 16;
const unsigned long scContour = 17;
const unsigned long scError = 18;
const unsigned long scPoint3D = 19;
const unsigned long scRadar = 20;
const unsigned long scClock = 21;
const unsigned long scWindRose= 22;
const unsigned long scBar3D = 23;
const unsigned long scImageBar = 24;
const unsigned long scDonut = 25;
const unsigned long scTriSurface = 26;
const unsigned long scBox = 27;
const unsigned long scHorizBox = 28;
const unsigned long scHistogram = 29;
const unsigned long scColorGrid = 30;
const unsigned long scBarJoin = 31;
const unsigned long scHighLow = 32;
const unsigned long scWaterfall = 33;
const unsigned long scSmith = 34;
const unsigned long scPyramid = 35;
const unsigned long scMap = 36;
const unsigned long scHorizLine = 37;
const unsigned long scFunnel = 38;
const unsigned long scCalendar = 39;
const unsigned long scHorizArea = 40;
const unsigned long scPointFigure = 41;
const unsigned long scGauge = 42;
const unsigned long scVector3D = 43;
const unsigned long scTower = 44;
const unsigned long scPolarBar = 45;
const unsigned long scBubble3D = 46;
const unsigned long scHorizHistogram = 47;
const unsigned long scVolumePipe = 48;
const unsigned long scIsoSurface = 49;
const unsigned long scDarvas = 50;
const unsigned long scHighLowLine = 51;
const unsigned long scPolarGrid = 52;
const unsigned long scDeltaPoint = 53;
const unsigned long scImagePoint = 54;
const unsigned long scOrganizational = 55;
const unsigned long scWorld = 56;
const unsigned long scTagCloud = 57;
const unsigned long scKagi = 58;
const unsigned long scRenko = 59;
const unsigned long scNumericGauge = 60;
const unsigned long scLinearGauge = 61;
const unsigned long scCircularGauge = 62;
const unsigned long scBigCandle = 63;
const unsigned long scLinePoint = 64;
//如需要相关图种,只需把上面代码
SeriesIndex.intVal= m_chart.AddSeries(scWaterfall);//把scWaterfall改为你所需图种的编号
m_chart.Series(0).GetAsWaterfall().SetIrregularGrid(true);// GetAsWaterfall改为你所需图种的相关函数名
----------------
一个完整的例子:
A、在CMyView(承载TeeChart的对话框视图)的定义中,添加VARIANTSeriesIndex;
B、在int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct)函数中:
int CMyView::OnCreate(LPCREATESTRUCTlpCreateStruct) { if(CFormView::OnCreate(lpCreateStruct) == -1) return-1; //TODO: Add your specialized creation code here m_chart.Create("",WS_VISIBLE, CRect(0, 0, 0, 0), this, 1234) ; m_chart.GetLegend().SetVisible(false);//隐藏图例 m_chart.GetAspect().SetView3D(true); //3D显示 m_chart.GetAxis().GetDepth().SetVisible(TRUE); //显示Z轴 m_chart.GetAxis().GetDepth().GetLabels().SetVisible(TRUE); //显示Z轴上的坐标 m_chart.GetAxis().GetDepth().GetLabels().SetStyle(0); //设置显示坐标的风格 //设置渐变背景 m_chart.GetPanel().GetGradient().SetVisible(true); m_chart.GetPanel().GetGradient().SetStartColor(RGB(192,192,192)); m_chart.GetPanel().GetGradient().SetEndColor(RGB(255,255,255)); //设置图标标题 m_chart.GetHeader().GetText().SetItem(0,COleVariant("瀑布图")); //开始绘制3D m_chart.RemoveAllSeries(); SeriesIndex.vt=VT_INT; SeriesIndex.intVal=m_chart.AddSeries(49);// 49号图种,IsoSurface类型3D m_chart.Series(0).GetAsIsoSurface().SetIrregularGrid(true); //设置曲线颜色 m_chart.Series(0).SetColor(RGB(255,0,0)); m_chart.GetAxis().GetBottom().SetMinMax(0,100); m_chart.GetAxis().GetBottom().GetLabels().SetValueFormat("0.0"); m_chart.GetAspect().SetChart3DPercent(30);//调整3D纵深比 return0; }
C、在自定义的消息处理函数中:
LRESULT CMyView::OnMyMsg(WPARAM wParam,LPARAM lParam) { staticdouble xVal = .0 ; doubleyVal = .0 ; CTSDoc* pDoc = (CTSDoc*)(this->GetDocument()) ; yVal= pDoc->clientDataBuff[0] ; for(int zVal=0; zVal<50; zVal++) m_chart.Series(0).GetAsIsoSurface().AddXYZ(xVal,yVal, (double)zVal, NULL,RGB(255,(int)(yVal*30)+160,0)); m_chart.Series(0).RefreshSeries(); if(m_chart.Series(0).GetCount() > 100) { m_chart.GetAxis().GetBottom().Scroll(1.0,true); //坐标轴一次移动1格 } xVal++; return0 ; }
四、
用AddArray一次把数组中的值绘制出来
1、先在TeeChart所在的类中 添加:
COleSafeArray XValues;
COleSafeArray YValues;
2、再在TeeChart的初始化设置函数添加:
DWORD numElements[] = {200000}; // Create the safe-arrays... XValues.Create(VT_R8, 1, numElements);YValues.Create(VT_R8,1, numElements); // Initialize them with values... long index; for(index=0; index<200000; index++) { double val = (double)index; XValues.PutElement(&index, &val); }; for(index=0; index<200000; index++) { double val = rand()%100; YValues.PutElement(&index, &val); };
3、在相应的位置添加:
m_chart.Series(0).AddArray(200000,YValues,XValues);
//附赠:本人搜集的一些TeeChart的相关资料。链接:http://download.csdn.net/detail/yang_yulei/7685021
//此文为网上首发,转载请注明出处,谢谢!
图表控件TeeChart干货分享(绘制2D、3D实时曲线---C++示例源代码--网络首发)