MFC+GDI+绘制出雷达余晖效果
1.首先要画出静态的坐标轴,用双缓冲方法在onpain消息中绘制。绘制方法都比较简单。声明一个内存DC,绘制一个圆形,再把坐标轴画上去。
void CDlg_RadarScanning::OnPaint()
{CPaintDC dc(this); // device context for painting// TODO: 在此处添加消息处理程序代码// 不为绘图消息调用 CDialogEx::OnPaint()GetClientRect(&WinRect);CenterPoint.X = WinRect.Height()/2.0;CenterPoint.Y = CenterPoint.X;ellipseRect2 = Rect(2,2,WinRect.Height()-4,WinRect.Height()-4); //外切矩形区域BitRect.Equals(ellipseRect2);pDC = this->GetDC();pDC->FillSolidRect(0,0,WinRect.right,WinRect.bottom,RGB(0,0,0));//随后建立与屏幕显示兼容的内存显示设备MemDC.DeleteDC();MemDC.CreateCompatibleDC(pDC);MemBitmap.DeleteObject();MemBitmap.CreateCompatibleBitmap(pDC,WinRect.Width(),WinRect.Height());//将位图选入到内存显示设备中//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);Graphics gp(MemDC.m_hDC);gp.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);//抗锯齿 REAL startAngle = 0.0f; // 设置起点角度REAL sweepAngle = 360.0f; // 设置旋转角度Brush* brush = NULL;brush = new SolidBrush(Color::Green);gp.FillEllipse(brush,ellipseRect2.Height/2,ellipseRect2.Height/2,5,5);//圆心// PointF* StarPoint = new PointF(1,ellipseRect2.Height/2);
// PointF* EndPoint= new PointF(ellipseRect2.Width,ellipseRect2.Height/2);PointF StarPoint(1,ellipseRect2.Height/2);PointF EndPoint(ellipseRect2.Width,ellipseRect2.Height/2+2);gp.DrawLine(&Pen(Color::Green,2),*(&StarPoint),*(&EndPoint));//水平坐标轴StarPoint.X = ellipseRect2.Width/2+2;EndPoint.X = StarPoint.X;StarPoint.Y = 1;EndPoint.Y = ellipseRect2.Height;gp.DrawLine(&Pen(Color::Green,2),*(&StarPoint),*(&EndPoint));//竖直坐标轴int Gap =ellipseRect2.Height/10+2;//刻度间隙int count = 5;for (int i=0;i<count;i++)//同心圆{gp.DrawArc(&Pen(Color::Green,2),ellipseRect2,startAngle,sweepAngle);//画圆ellipseRect2.X = ellipseRect2.X +Gap;ellipseRect2.Y = ellipseRect2.Y +Gap;ellipseRect2.Width = ellipseRect2.Width - 2*Gap;ellipseRect2.Height = ellipseRect2.Height-2*Gap;}FontFamily fontFamily(L"微软雅黑"); Gdiplus::Font myFont(&fontFamily, 10, FontStyleRegular, UnitPoint); //第二个是字体大小SolidBrush blackBrush(Color(255, 215, 0)); //半透明+文字RGB颜色int iOverrange = WinRect.Height()/10;CString strTemp;StringFormat format; format.SetAlignment(StringAlignmentNear); //文本排列方式,即在对应位置居中、靠左、靠右for (int i=5;i>=0;i--)//标明刻度{strTemp.Format("%d",i*2);WCHAR* wchar= strTemp.AllocSysString(); //wcscpy_s(string, CT2CW(str)); //如果使用MFC中的CString,需要这样转换成WCHARPointF school_site((REAL)(5-i)*iOverrange, (REAL)WinRect.Height()/2);//文字放置的像素坐标gp.DrawString(wchar, wcslen(wchar), &myFont, school_site, &format, &blackBrush );//把string绘制到图上 }strTemp.Format("刻度/KM");WCHAR* wchar1= strTemp.AllocSysString();PointF school_site1((REAL)2, (REAL)2);//文字放置的像素坐标gp.DrawString(wchar1, wcslen(wchar1), &myFont, school_site1, &format, &blackBrush );////pDC->TransparentBlt(0,0,WinRect.Height()-1,WinRect.Height()-1,&MemDC,0,0,ellipseRect2.Width,ellipseRect2.Height,TRANSPARENT);pDC->BitBlt(0,0,WinRect.Height()-1,WinRect.Height()-1,&MemDC,0,0,SRCCOPY);m_iMetersPerPx = 10000.0/(WinRect.Height()/2.0);//10KM与图像刻度比例MemDC.SelectObject(pOldBit);gp.ReleaseHDC(MemDC.m_hDC);}
2.其次是绘制一个渐变的扇形区域,原点和角度要定好,而且要求是能够旋转的扇形。从简单的旋转开始就是模仿秒针旋转。一开始我也是只绘制一根线旋转。图形旋转时会留有上一次的图形,所以我们还需要把上一次角度的图形抹掉,重新定位新的角度的图形。我选择的办法是开启一个定时器,用之前画好的内存DC不断地去覆盖上一张图,在覆盖完成后再在上面绘制新角度的旋转图形即可。图形旋转我是用矩阵旋转。
//该代码写在定时器即可static double fPi = 4.0*atan(1.0);//180度double fAngle = fPi/2,fDAngle = 2.0*fPi/60.0;//360度分成60份//转盘看为时钟盘,摆针为秒针SYSTEMTIME tmNow;GetLocalTime(&tmNow);//s为中心点,e为变化后的点,两点连起来形成摆针//Pen sPen(Color(255, 0, 0), 3); CPen mypen(PS_SOLID,2,RGB(255,255,255));CPen* pOldPen;CDC* MyDC = this->GetDC();pOldPen=MyDC->SelectObject(&mypen);Point s(CenterPoint.X,CenterPoint.Y), e; double fTime = tmNow.wSecond + tmNow.wMilliseconds/1000.0 + 0;//以系统时间为当前比例//double fTime =30;double fAng = fPi/2.0 - fTime * (2.0*fPi) /30.0;//360度分的刻度影响摆针旋转速度,分得越少越快e.X = (int)(CenterPoint.X + (WinRect.Height()/2.0) * cos(fAng)); e.Y = (int)(CenterPoint.Y - (WinRect.Height()/2.0) * sin(fAng));MyDC->BitBlt(0,0,WinRect.Height(),WinRect.Height(),&MemDC,0,0,SRCCOPY);//将内存位图贴上再划线可将上一次图形覆盖//旋转扇形static int angl = 10;//每次旋转角度Graphics graphics(MyDC->m_hDC);graphics.SetSmoothingMode(SmoothingModeAntiAlias);//矩阵旋转Matrix matrix;matrix.Translate(CenterPoint.X, CenterPoint.Y, MatrixOrderAppend);//matrix.Scale(2, 2, MatrixOrderAppend);matrix.RotateAt(angl, PointF(CenterPoint.X, CenterPoint.Y), MatrixOrderAppend);matrix.Translate(-CenterPoint.X, -CenterPoint.Y);graphics.SetTransform(&matrix); LinearGradientBrush linGrBrush(Point(0,0),Point(120,120),Color(0,255,255,255),Color(255,0,255,100));//渐变画刷//graphics.DrawPie(&Pen(Color::Green,2),(int)0, (int)0,WinRect.Height(),WinRect.Height(),0,60);graphics.FillPie(&linGrBrush,(int)0, (int)0,WinRect.Height(),WinRect.Height(),0,60);//绘制渐变扇形//graphics.FillRectangle(&linGrBrush,(int)CenterPoint.X,(int)CenterPoint.Y, 50, WinRect.Height()/2);angl++;//以下均为旋转秒针
// graphics.DrawLine(&Pen(Color::White,2),(int)CenterPoint.X,(int)CenterPoint.Y,(int)e.X,(int)e.Y);
// MyDC->MoveTo(CenterPoint.X,CenterPoint.Y);
// MyDC->LineTo(e.X,e.Y);MyDC->SelectObject(pOldPen);ReleaseDC(MyDC);pOldPen->DeleteObject();mypen.DeleteObject();
3.下面是添加目标。新建一个透明的对话框叠加到当前雷达扫描的对话框,新对话框重绘透明的按钮类,再贴图到按钮上,还要添加按钮的点击响应消息。然后在雷达扫描窗口上声明新窗口的对象Create窗口。至于每一个按钮都是对应一个封装的结构体,里面含有距离、高度和按钮句柄等信息,不断地调用movewindow即可实现移动。
//.h
struct FlyBtInfo
{double iDistance;double iHeight;CCDrawButtonTM flyBt;//句柄BOOL bShow;BOOL bSelected;
};
CDlg_DrawFly *m_DrawFlyDlg;
FlyBtInfo m_DrawFly[3];//.cpp
if(m_DrawFlyDlg==NULL){m_DrawFlyDlg = new CDlg_DrawFly(this);m_DrawFlyDlg->Create(IDD_DLG_DRAWFLY,this);m_DrawFlyDlg->ShowWindow(SW_SHOW);}memcpy(&m_DrawFly[0].flyBt,&m_DrawFlyDlg->m_OneFlyBt,sizeof(CCDrawButton));m_DrawFly[0].bShow=FALSE;m_DrawFly[0].bSelected =FALSE;memcpy(&m_DrawFly[1].flyBt,&m_DrawFlyDlg->m_TwoFlyBt,sizeof(CCDrawButton));m_DrawFly[1].bShow=FALSE;m_DrawFly[1].bSelected =FALSE;memcpy(&m_DrawFly[2].flyBt,&m_DrawFlyDlg->m_ThreeFlyBt,sizeof(CCDrawButton));m_DrawFly[2].bShow=FALSE;m_DrawFly[2].bSelected =FALSE;//第一个定时器
//飞机1static int x1 =WinRect.Height()/2.0*(1-sin(3.14/4));if (x1<(WinRect.Height()/2)){m_DrawFly[0].flyBt.MoveWindow(x1-10,x1-10,25,25);//m_DrawFly[0].flyBt.Invalidate();m_DrawFly[0].flyBt.ShowWindow(SW_SHOW);//根据刻度算出距离,高度这里为十分之一距离double a =WinRect.Height()/2-x1+10;m_DrawFly[0].iDistance = sqrt(pow(a,2)*2)*m_iMetersPerPx;m_DrawFly[0].iHeight = m_DrawFly[0].iDistance/10;if (m_DrawFly[0].bSelected){UpdateFlyDataToList(0);}x1 ++;} else{x1 =WinRect.Height()/2.0*(1-sin(3.14/4));}//飞机2static int x2 = WinRect.Height()/2-5;static int y2 =WinRect.Height()/2-5;int x21 = WinRect.Height()/2*(1+sin(3.14/4));int y21 = WinRect.Height()/2*(1-sin(3.14/4));if (x2<x21 || y2>y21){m_DrawFly[1].flyBt.MoveWindow(x2,y2,25,25);//m_DrawFly[1].flyBt.Invalidate();m_DrawFly[1].flyBt.ShowWindow(SW_SHOW);double a1 = pow(WinRect.Height()/2.0-x2,2);double a2 = pow(WinRect.Height()/2.0-y2,2);m_DrawFly[1].iDistance = sqrt(a1+a2)*m_iMetersPerPx;m_DrawFly[1].iHeight = m_DrawFly[1].iDistance/10;if (m_DrawFly[1].bSelected){UpdateFlyDataToList(1);}x2 ++;y2 --;}else{x2 = WinRect.Height()/2-5;y2 =WinRect.Height()/2-5;}
下面是用到的几个方法:
//更新列表数据
void CDlg_RadarScanning::UpdateFlyDataToList(int iTranceBatch)
{int BatchTemp;int count = m_FlydataList.GetItemCount();for (int i =0;i<count;i++){BatchTemp = atoi(m_FlydataList.GetItemText(i,0));if (BatchTemp == iTranceBatch){CString strDistance;strDistance.Format("%.2f",m_DrawFly[iTranceBatch].iDistance);m_FlydataList.SetItemText(i,1,strDistance);strDistance.Format("%.2f",m_DrawFly[iTranceBatch].iHeight);m_FlydataList.SetItemText(i,2,strDistance);}}
}
//点击按钮时切换位图
void CDlg_RadarScanning::ChangeFlyColor(int iTranceBatch)
{switch(iTranceBatch){case 0:m_DrawFlyDlg->m_OneFlyBt.UpdateBtn("res\\airport.png","res\\airport.png","res\\airport.png","res\\airport.png","");m_DrawFlyDlg->m_OneFlyBt.Invalidate();m_DrawFlyDlg->m_TwoFlyBt.UpdateBtn("res\\airport4.png","res\\airport4.png","res\\airport4.png","res\\airport4.png","");m_DrawFlyDlg->m_TwoFlyBt.Invalidate();break;case 1:m_DrawFlyDlg->m_OneFlyBt.UpdateBtn("res\\airport2.png","res\\airport2.png","res\\airport2.png","res\\airport2.png","");m_DrawFlyDlg->m_OneFlyBt.Invalidate();m_DrawFlyDlg->m_TwoFlyBt.UpdateBtn("res\\airport5.png","res\\airport5.png","res\\airport5.png","res\\airport5.png","");m_DrawFlyDlg->m_TwoFlyBt.Invalidate();break;}
}//按钮点击消息,通过发送消息给主窗口类
void CDlg_DrawFly::OnBnClickedOnedrawbutton()
{m_Parent->SendMessage(WM_DRAWFLY_BT_MSG,IDC_ONEDRAWBUTTON,0);// TODO: 在此添加控件通知处理程序代码
}
void CDlg_DrawFly::OnBnClickedTwodrawbutton()
{// TODO: 在此添加控件通知处理程序代码m_Parent->SendMessage(WM_DRAWFLY_BT_MSG,IDC_TWODRAWBUTTON,0);
}
void CDlg_DrawFly::OnBnClickedThreedrawbutton()
{// TODO: 在此添加控件通知处理程序代码m_Parent->SendMessage(WM_DRAWFLY_BT_MSG,IDC_THREEDRAWBUTTON,0);
}//这个方法用于响应按钮点击事件
LRESULT (主窗口类)::OnDrawFlyBTDepose(WPARAM wParam, LPARAM lParam)
{switch(wParam){case IDC_ONEDRAWBUTTON:{m_RadarScanningDlg->ChangeFlyColor(0);m_RadarScanningDlg->m_CurSelectAim =0;m_RadarScanningDlg->m_DrawFly[0].bSelected =TRUE;CString strTemp;strTemp.Format("00 跟踪");m_RadarScanningDlg->m_FlydataList.SetItemText(0,0,strTemp);m_RadarScanningDlg->m_FlydataList.SetItemText(1,0,"01");}//SelectRadarTrackBatch(0);break;case IDC_TWODRAWBUTTON:{m_RadarScanningDlg->ChangeFlyColor(1);m_RadarScanningDlg->m_CurSelectAim =1;m_RadarScanningDlg->m_DrawFly[1].bSelected =TRUE;CString strTemp;strTemp.Format("01 跟踪");m_RadarScanningDlg->m_FlydataList.SetItemText(1,0,strTemp);m_RadarScanningDlg->m_FlydataList.SetItemText(0,0,"00");}//SelectRadarTrackBatch(1);break; }return 1;
}
总结:
使用MFC确实不好做,我查了一下很多都是QT。关键是GDI+绘图和矩阵旋转比较难。虽然这个是我自己写的,但是工程是公司的,我不好公开全部代码,有什么问题可以问我。
以下是提供可学习
1.GDI+学习及代码总结之------画线、区域填充、写字
https://blog.csdn.net/harvic880925/article/details/9023329
2.GDI+绘制极坐标图、雷达图
https://blog.csdn.net/iteye_15968/article/details/82334555
3.GDI+旋转图片的几种方法
https://blog.csdn.net/fyl_077/article/details/44456213