先上效果图
一、如何绘制动态曲线
所谓动画,都是一帧一帧的图像连续呈现在用户面前形成的。所以如果你掌握了如何绘制静态曲线,那么学会绘制动态曲线也不远啦,只需要创建一个定时器(比如调
用MFC中的SetTimmer函数),每隔一定时间(比如1ms),调用OnPaint或者OnDraw函数,绘制当前帧图像即可。
这里需要注意的是,绘制图像的代码需要写在OnPaint或者OnDraw函数中,因为当窗口失效(比如最小化)恢复后,会重新绘制当前窗口,窗口之前的自绘图像会丢失。而把绘图代码写在OnPaint或者OnDraw中就是为了让窗口每次重绘时也能重绘你自己画的图像,避免出现窗口最小化再恢复后,自己画的图像丢失的尴尬情况。
另外绘制当前帧图像之前,记得用InvalidateRect函数清除上一帧图像,不然各帧图像会背景的堆叠。
比如我想清除窗口中(0,0)和(100,100)这两点确定的矩形中的图像,代码如下:
根据上面的思路,我们每隔一定时间绘制一幅图像,可是如果每次绘制的图像都是完全相同的,那么图像看起来也是静态的。如何让曲线动起来呢?我们需要为自己绘
图的代码设计一个输入,即在当前时刻曲线上各个点的坐标信息。随着时间的推移,令曲线上各个点的坐标随之变化,这样每次绘图都是基于当前时刻的曲线坐标绘制的,控制好曲线坐标的变化,也就能让你绘制的曲线乖乖的动起来。
上面提到了曲线上各个点的坐标信息,这个信息可以用多种数据结构储存,不过笔者推荐使用STL中的deque数据结构储存。为什么呢?需求决定选择。让我们先想想在绘制图像的过程中需要对这个数据进行哪些操作。
1、需要遍历这个数据,获取各个点的坐标以便绘图,所以选择的数据结构必须有较高的遍历效率。
2、当曲线上的点横向上充满了横坐标轴提供的显示范围,需要将曲线最右边的点的坐标移除,然后在曲线最左边添加下一个新点的坐标,以实现曲线向右平移的效果。所以选择的数据结构需要支持前端和后端元素的添加删除操作,大家很自然会想到队列。
STL中的list容器也能很轻松的实现队列功能,但是list还支持任意位置元素的添加和删除操作,功能上的冗余决定了list需要花费更多的时间来实现我们的需求,事实上遍历一个deque常常比遍历一个list快几十倍,原因在这里就不赘述啦。
于是,笔者构建了这样的数据结构
deque<pair<TIME, VALUE>> m_dqDisplayData;
队列中的每个元素是一个pair,pair中存放坐标。维护这个数据结构的核心代码如下:
//如果队列长度超过了X轴方向上可绘的所有点的数量
1、画坐标轴的函数,你们看,我就是让 ”内存画家“ --- MemDC 画画的,表示用了双缓冲的哦,呵呵。 void CXXXDlg::DrawAxis(CDC &MemDC, LPTSTR TitleForX, LPTSTR TitleForY) { //选择画坐标轴的画笔 CPen PenForDrawAxis(PS_SOLID, 1, RGB(0, 128, 64)); MemDC.SelectObject(PenForDrawAxis); //绘制X轴 MemDC.MoveTo(60, 220); MemDC.LineTo(520, 220); //绘制箭头 MemDC.LineTo(510, 223); MemDC.LineTo(510, 217); MemDC.LineTo(520, 220); //绘制Y轴 MemDC.MoveTo(60, 220); MemDC.LineTo(60, 30); //绘制箭头 MemDC.LineTo(57, 40); MemDC.LineTo(63, 40); MemDC.LineTo(60, 30); //设置文本的颜色 COLORREF OldColor = MemDC.SetTextColor(RGB(255, 255, 0)); //绘制标注 MemDC.TextOut(480, 230, TitleForX); MemDC.TextOut(40, 10, TitleForY); //还原文本颜色 MemDC.SetTextColor(OldColor); } 2、画图例的函数 void CXXXDlg::DrawLegend(CDC &MemDC, CPen &PenForDraw, CPen &PenForDrawAB, CPen &PenForDrawBE) { //设置文本的颜色 COLORREF OldColor = MemDC.SetTextColor(RGB(0, 128, 64)); //绘制图例 MemDC.SelectObject(PenForDraw); MemDC.TextOut(530, 30, _T("Global")); MemDC.MoveTo(530, 50); MemDC.LineTo(570, 50); MemDC.SelectObject(PenForDrawAB); MemDC.TextOut(530, 70, _T("AB")); MemDC.MoveTo(530, 90); MemDC.LineTo(570, 90); MemDC.SelectObject(PenForDrawBE); MemDC.TextOut(530, 110, _T("BE")); MemDC.MoveTo(530, 130); MemDC.LineTo(570, 130); //还原文本颜色 MemDC.SetTextColor(OldColor); } 3、画曲线的函数 void CXXXDlg::DrawDynamicCurve(CDC &MemDC, CPen &Pen, deque<pair<TIME, VALUE>> &DisplayData, double Proportion) { //选择画笔 MemDC.SelectObject(Pen); //进入临界区 EnterCriticalSection(&(m_cControllingParameters.m_cCriticalSection)); //绘制曲线 if (DisplayData.size() >= 2) { COORDINATE XPos = 60; for (UINT PointIndex = 1; PointIndex != DisplayData.size(); PointIndex++) { MemDC.MoveTo(XPos++, 220 - (COORDINATE)((double)(DisplayData[PointIndex - 1].second) / Proportion)); MemDC.LineTo(XPos, 220 - (COORDINATE)((double)(DisplayData[PointIndex].second) / Proportion)); } } //离开临界区 LeaveCriticalSection(&(m_cControllingParameters.m_cCriticalSection)); //还原文本颜色 MemDC.SetTextColor(OldColor); } 4、重点来啦,Onpaint函数,有双缓冲技术的主流程 void CXXXDlg::OnPaint() { CDC *pDC = GetDC(); //创建一个内存中的显示设备 CDC MemDC; MemDC.CreateCompatibleDC(NULL); //创建一个内存中的图像 CBitmap MemBitmap; MemBitmap.CreateCompatibleBitmap(pDC, 580, 250); //定义各种类型的画笔 CPen PenForDraw(PS_SOLID, 1, RGB(0, 232, 255)); CPen PenForDrawAB(PS_SOLID, 1, RGB(0, 98, 0)); CPen PenForDrawBE(PS_SOLID, 1, RGB(221, 0, 221)); //指定内存显示设备在内存中的图像上画图 MemDC.SelectObject(&MemBitmap); //先用一种颜色作为内存显示设备的背景色 MemDC.FillSolidRect(0, 0, 580, 250, RGB(1,4,1)); //绘制坐标轴 DrawAxis(MemDC, _T("time(s)"), _T("length(kbit)")); //绘制图例 DrawLegend(MemDC, PenForDraw, PenForDrawAB, PenForDrawBE); //绘制曲线 DrawDynamicCurve(MemDC, PenForDraw, m_dqDisplayData, 1000); //将内存中画好的图像直接拷贝到屏幕指定区域上 pDC->BitBlt(20, 50, 580, 250, &MemDC, 0, 0, SRCCOPY); //释放相关资源 ReleaseDC(pDC); }
原文:http://www.cnblogs.com/arxive/p/4911098.html