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,需要这样转换成WCHAR
PointF 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;
//第一个定时器
//飞机1
static 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));
}
//飞机2
static 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