MFC学习笔记(一)High Speed Chart 控件使用

引言

大学期间学习STM32时,由于参加机器人比赛,第一次接触到摄像头,让我深深被其吸引,还记得当初为了点亮第一颗摄像头,第一次通过自己写的颜色识别算法让它按照自己的意愿去识别出每帧图像的内容,正确标识出物体位置信息时的激动,那时的我们真的是废寝忘食般的吸收大量新知识,连续通宵好几晚,最终,付出的辛苦使我们收获颇丰。刚大学毕业时,我是在一家医疗器械公司担任硬件工程师,但是出于对摄像头及与其相关的图像识别算法的热爱,我毅然决然的辞掉了硬件工程师的工作,重新应聘了一家做摄像头的公司。在其中任职软件研发人员,终于让我得以慢慢掀开摄像头的神秘的面纱。初来公司就遇到了一个比较大的挑战,由于公司项目的需要,原先写的一种测试软件已经较为古老,不太适用于Windows 10以上的版本,而且随着windows系统的不断更新,老软件经常出现卡死的现象。因此,公司决定开发一个新的软件,用于替换原先的旧软件。其实也不能叫重新开发软件,因为公司自己开发了一个比较成熟的测试软件,是用MFC写的,而我们软件工程师主要做的也就是在主框架中开发新的DLL,部门老大出于锻炼新人的想法,最后将开发测试DLL的任务就交给我这个新人了。
对于我这个新人来说,这还是相当困难的,因为我之前从来没接触过MFC,就连C++也是学得很肤浅的,虽然有旧软件的源码,可以参考着来进行修改,但是当我具体去看源码的时候才感觉到没有注释是多么的痛苦,定义的一大堆不明其意的函数及各种类,都没有注释,看得我是一个头两个大。(所以在这里,我衷心希望我们所有的软件攻城狮们都能有良好的代码注释习惯,这样不仅能提醒自己,也能帮助后来者,让他们也能很好地看懂代码。)看了两天,我放弃了,感觉读懂那个源码所需要花的时间太长了,还不如自己重新一个,可是自己写也很头痛啊,这个DLL关键的是要有折线图实时显示,而MFC中又没有专门的绘图插件,原先那个软件是自己绘制的图形,对我这个新人而言肯定没办法做到的,最后总算在CSDN中找到了合适的绘图控件:High Speed Chart和TeeChart。感谢尘中远大神写的High Speed Chart得用法介绍,帮助很大。原文出处:MFC下好用的高速绘图控件-(Hight-Speed Charting)

High Speed Chart的安装教程网上有很多,尘中远大神的教程已经很详细了,我就不再重复说明了,这里我主要介绍我在使用这个控件时遇到的问题,及解决的办法。下图是效果图:

测试图片显示

一、High Speed Chart 控件使用步骤

使用前先声明几个指针对象

CChartCtrl m_ChartCtrl;
CChartLineSerie *pLineSerie1;
CChartLineSerie *pLineSerie2;
CChartAxis *pAxis1; 
CChartAxisLabel* pLabel;

这几个指针是使用控件所必需要的,下面介绍步骤

1、建立坐标轴线系

使用到的函数主要有以下几个:
CreateStandardAxis(CChartCtrl::LeftAxis); SetAutomatic(false); SetMinMax(-20,450); CreateStandardAxis(CChartCtrl::BottomAxis); SetAutomatic(false); SetMinMax(-50,1100);
其中,CreateStandardAxis()函数表示创建标准轴,参数有四个可选

enum EAxisPos
	{
		LeftAxis = 0,
		BottomAxis,
		RightAxis,
		TopAxis
	};

分别表示左、下、右、上四个坐标轴。
SetAutomatic(false); SetMinMax(-20,450);
这两个函数中SetAutomatic()函数用于设置是否使能自动绘制坐标轴线系,具体效果可以自己去测试一下,SetMinMax()函数用于手动绘制坐标轴线系时设置坐标的最小和最大值。当使用此函数时,SetAutomatic()函数的参数必须设置为false。

2、设置控件图示信息

一张图表肯定是会有表头、坐标轴这些图示的,High Speed Chart中提供的绘制图示的方法还是挺简单的,通过以下代码即可实现图示信息的绘制

str1 = _T("表头");
m_ChartCtrl.GetTitle()->AddString(str1);
str1 = _T(" 左坐标轴");
pAxis1 = m_ChartCtrl.GetLeftAxis();
if(pAxis1)
		pLabel = pAxis1->GetLabel();
if(pLabel)
		pLabel->SetText(str1);
str1 = _T("下坐标轴");
pAxis1 = m_ChartCtrl.GetBottomAxis();
if(pAxis1)
		pLabel = pAxis1->GetLabel();
if(pLabel)
		pLabel->SetText(str1);

3、设置线系

	pLineSerie1 = m_ChartCtrl.CreateLineSerie();
	pLineSerie1->SetSeriesOrdering(poNoOrdering);//设置为无序
	pLineSerie2 = m_ChartCtrl.CreateLineSerie();
	pLineSerie2->SetSeriesOrdering(poNoOrdering);//设置为无序
	m_ChartCtrl.RemoveAllSeries();

上面的两个函数用于创建坐标轴每个点连接的线系,以及后面折线图的实时显示也要用到它,但是函数的具体含义我也依然不太清楚,RemoveAllSeries()函数用于清除线系,主要是清除之前绘制图形时建立的线系,保证之后再次绘制时不出错

4、绘制点

void Image_ShowDlg::DrawPointMoving()
{
	m_ChartCtrl.EnableRefresh(false);
	//画图测试
		X_Axis1[m_count]=m_count;
		Y_Axis1[m_count]=m_count/3.0;
		pLineSerie1->AddPoint(X_Axis1[m_count],Y_Axis1[m_count]);
	m_ChartCtrl.EnableRefresh(true);
}

画点函数主要有AddPoint(); AddPoints();这两个,AddPoint()函数表示绘制单个点,其参数为点的(X,Y)坐标值,而AddPoints()函数表示连续绘制点,其参数为所有点的(X,Y)坐标值,绘图时AddPoints()比AddPoint()函数执行速度要快很多,可以根据实际情况选择合适的函数。其中还有一个函数需要注意,EnableRefresh()这个函数主要是用于将改变显示到控件图形中,在需要对某个值进行修改时必须先执行EnableRefresh(false);再执行EnableRefresh(true);

二、折线图实时显示功能实现方法

我是通过MFC的定时器功能来实现实时显示功能的,在定时器中添加一个画点函数,每当定时器中断产生时就进入画点函数一次,这样重复画点直到最后一个点画完,在我们肉眼直观看着也就相当于实时显示了。
具体代码如下:

void Image_ShowDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if(0 == nIDEvent)
	{
		flag=0;   //绘制上升曲线时不使能下降曲线绘制
		if (m_count1>=MaxCode) //MaxCode表示要画的点在坐标轴上的最大值
		{
			KillTimer(0);
			SetTimer(1,20,NULL);
		}
		else 
		{
			DrawPointMoving();
		}
		m_count1+=10;
	}
	if(1 == nIDEvent)
	{
		flag=1;  //使能下降曲线绘制
		if (m_count2<=MaxCode&&m_count2>=MinCode)
		{
			DrawPointMoving();
			m_count2-=10;
		}
		else if (m_count2<MinCode)//MinCode表示要画的点在坐标轴上的最小值
		{
			KillTimer(1);
		}
 	}
	CDialog::OnTimer(nIDEvent);
}

三、控件使用中遇到的问题点总结

1、控件对话框弹出时快时慢

原因分析:上面的定时器中的代码是我删减后的,其实还有一部分代码没有附上,电脑虽然运行速度很快,但是也是有极限的,我最初将定时器时间设置得很短,导致出现这种问题。设置定时器时间的函数是 SetTimer(1,20,NULL); 其中第2个参数代表定时时间,单位是ms。我一开始为了追求最快运行速度将定时时间设置在了20ms以下,这样最终就导致了对话框弹出时快时慢的问题,仔细分析以后我觉得出现这种问题的原因是:在前一个定时中断产生时,其定时函数中的代码还未执行完或者刚执行完下一个中断就产生了,这样就导致了虽然控件已经更新了但是界面还来不及刷新,一直持续到有足够的时间来执行界面刷新时才弹出对话框。

2、控件连续运行时其设备环境异常,导致内存访问冲突

此问题困扰了我有接近三天的时间,每天就用VS不停地调试,设置各种断点、查看出现内存访问冲突的代码段,通过堆栈空间一步步的往下寻找,终于找到出现问题的原因点。在 ChartCtrl.h 这个头文件中定义了这样两个对象:

CChartAxis* m_pAxes[4];
CDC m_BackgroundDC;

第一个指针对象m_pAxes[4],它是用来存储坐标轴信息的,也就是上文提到的左、下、右、上四个坐标轴;
第二个对象是 CDC 类对象,是控件的DC(设备环境),也就是用来画图的,控件之所以能显示出图形就是用到了这个类;
由于我开发的这个DLL要运行在软件主框架中,要求能对摄像头进行连续测试,也就是说在第一次测试完成后不能关闭软件,要继续进行第二次测试。实际运行时就发现,程序在第一次运行时没问题,第二次运行时就出现了内存访问冲突的问题,仔细排查后发现是由于控件的DC没有释放导致的,因为第二次运行时进入对话框初始化函数中又要执行
pAxis1 = m_ChartCtrl.CreateStandardAxis(CChartCtrl::LeftAxis); pAxis1->SetAutomatic(false);
这两个函数,而这两个函数在执行时要再次创建DC,这时由于之前没有释放DC就导致了内存访问冲突的发生。要解决这个问题只需要对DC进行释放就可以了,但是在 ChartCtrl.h 这个头文件中,控件编写者将它们放在了private: 后面,也就是说想要释放DC你必须通过CChartCtrl类中定义的函数才能访问。因此,要想解决DC这个问题你有两种方法:一种是在CChartCtrl类中定义一个函数,通过这个函数去访问DC就可以手动释放DC了;第二种是将CDC m_BackgroundDC;放到public: 中,这样你可以在类外部定义一个对象就可以直接访问DC了。但是通常不建议采用第二种方法,因为容易出现其他的问题。轴系m_pAxes[4]也是通过一样的方法就可以对其进行修改了。

四、总结

作为一个刚从硬件工程师转职软件工程师的人来说,软件这门课太深奥了,有相当相当多的难点摆在我的面前,我需要花比别人更多的时间才能完成同样的工作。一开始想起做硬件工程师时的得心应手我也在想,我是不是太冲动了,好好的硬件不做偏要随着自己的性子来选择可能会影响自己下半生生活的职业,一个吃青春饭的行业。但是,我在大学时就已经下定决心以后要从事图像处理相关的工作,毕业设计之所以选择“小车自主泊车系统”这个题目不也是这个原因吗?每当想起这些我就能更加坚定我要从事软件工作的决心,为了自己的梦想与目标,对自己说一句:加油!

有需要这个控件的小伙伴请点击下面的链接:
MFC动态绘图控件High Speed Chart

猜你喜欢

转载自blog.csdn.net/weixin_42171170/article/details/83748590