实现一个雷达扫描图。

源代码在TK_King/雷达 (gitee.com),自行下载就好了

制作思路

  1. 绘制圆形(或者称之轮)
  2. 绘制分割线
  3. 绘制扫描范围
  4. 添加扫描点

具体实现

首先我们使用自定义的控件。你可以使用vs自动添加,也可以手动创建类。注意手动创建时要创建Themes/Generic.xaml的文件路径哦。

控件继承自itemscontrol,取名叫做Radar。

我们第一步思考如何实现圆形或者轮,特别是等距的轮。

我们可以使用简单的itemscontrol的WPF控件,通过自定义ItemTemplate就可以简单的创建了。

因为要显示圆,所以使用Ellipse是最简单的事情。

又因为要在同一个区域内,显示同心圆,我们将面板改为Grid,利用叠加的特性去构造同心圆。

既然我们用了itemscontrol 来承载圈轮,直接让这个圈可自定义呢?

所以,我们构造一个集合依赖属性。关于集合依赖属性我们可以参加MSDN集合类型依赖属性 - WPF .NET | Microsoft Docs

  1. /// <summary>
  2. /// 每圈的大小
  3. /// </summary>
  4. public FreezableCollection<RadarSize> RadarCircle
  5. {
  6. get { return (FreezableCollection<RadarSize>)GetValue(RadarCircleProperty); }
  7. set { SetValue(RadarCircleProperty, value); }
  8. }
  9.  
  10. /// <summary>
  11. /// 每圈的大小
  12. /// </summary>
  13. public static readonly DependencyProperty RadarCircleProperty =
  14. DependencyProperty.Register("RadarCircle", typeof(FreezableCollection<RadarSize>), typeof(Radar), new PropertyMetadata(new PropertyChangedCallback(OnRadarCircelValueChanged)));

对应泛型类可以参考源代码,基本元素就是绑定ellipse的参数

  1. <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic" ItemsSource="{TemplateBinding RadarCircle }">
  2. <ItemsControl.ItemsPanel>
  3. <ItemsPanelTemplate>
  4. <Grid IsItemsHost="True"/>
  5. </ItemsPanelTemplate>
  6. </ItemsControl.ItemsPanel>
  7. <ItemsControl.ItemTemplate>
  8. <DataTemplate>
  9. <Ellipse Width="{Binding Width}" Height="{Binding Height}" Stroke="{Binding Color}"/>
  10. </DataTemplate>
  11. </ItemsControl.ItemTemplate>
  12. </ItemsControl>

哇啦,图像就出来了。

同理,我们创建分割线也是同样的过程。

对于分割线的切割算法,我们使用圆上点的坐标可以通过( rcos,rsin)=》(x,y) ,也就是极坐标。

关于此部分代码是放在布局块内ArrangeOverride,也可以放置在OnReader。

下面是局部代码,完整可以参考源代码

  1.         var angle = 180.0 / 6;
  2. circlesize = size.Height > size.Width ? size.Width : size.Height;
  3. RadarFillWidth = circlesize;
  4. var midx = circlesize / 2.0;
  5. var midy = circlesize / 2.0;
  6. circlesize = circlesize / 2;
  7. RadarRadius = circlesize;
  8. //默认为6个
  9. for (int i = 0; i < 6; i++)
  10. {
  11. var baseangel = angle * i;
  12. var l1 = new Point(midx + circlesize * Math.Cos(Rad(baseangel)), midy - circlesize * Math.Sin(Rad(baseangel)));
  13. var half = baseangel + 180;
  14. var l2 = new Point(midx + circlesize * Math.Cos(Rad(half)), midy - circlesize * Math.Sin(Rad(half)));
  15. RadarLineSize radarLine = new RadarLineSize();
  16. radarLine.Start = l1;
  17. radarLine.End = l2;
  18. radarLine.Color = RadarLineColor;
  19. RadarLine.Add(radarLine);
  20. }
  21. return size;

依赖属性

  1.     /// <summary>
  2. /// 雷达图的分割线,目前固定为6,可以自行修改
  3. /// </summary>
  4. public FreezableCollection<RadarLineSize> RadarLine
  5. {
  6. get { return (FreezableCollection<RadarLineSize>)GetValue(RadarLineProperty); }
  7. set { SetValue(RadarLineProperty, value); }
  8. }
  9.  
  10. /// <summary>
  11. /// 雷达图的分割线,目前固定为6,可以自行修改
  12. /// </summary>
  13. public static readonly DependencyProperty RadarLineProperty =
  14. DependencyProperty.Register("RadarLine", typeof(FreezableCollection<RadarLineSize>), typeof(Radar));

xaml代码

  1.              <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic2" ItemsSource="{TemplateBinding RadarLine }">
  2. <ItemsControl.ItemsPanel>
  3. <ItemsPanelTemplate>
  4. <Grid IsItemsHost="True"/>
  5. </ItemsPanelTemplate>
  6. </ItemsControl.ItemsPanel>
  7. <ItemsControl.ItemTemplate>
  8. <DataTemplate>
  9. <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="{Binding Color}"/>
  10. </DataTemplate>
  11. </ItemsControl.ItemTemplate>
  12. </ItemsControl>

下一步就是扇形扫描了。

我们使用一个完整的圆,将其内部颜色填充为线性刷就可以得到一个效果不错的扫描了。

  1.     /// <summary>
  2. /// 雷达扫描的颜色
  3. /// </summary>
  4. public Brush RadarColor
  5. {
  6. get { return (Brush)GetValue(RadarColorProperty); }
  7. set { SetValue(RadarColorProperty, value); }
  8. }
  9.  
  10. /// <summary>
  11. /// 雷达扫描的颜色
  12. /// </summary>
  13. public static readonly DependencyProperty RadarColorProperty =
  14. DependencyProperty.Register("RadarColor", typeof(Brush), typeof(Radar));

为了更好的定义这个圆,我们将radar的template使用grid面板等距分成四个区域(其实没啥用,主要是为了扇形扫描时做圆心选择的line,也可以不分成四个)。

在考虑动画,只需要做圆形360的选择就可以了。为了更好应用,我们创一个paly的依赖属性来播放动画。

  1.      /// <summary>
  2. /// 是否播放动画
  3. /// </summary>
  4. public bool Play
  5. {
  6. get { return (bool)GetValue(PlayProperty); }
  7. set { SetValue(PlayProperty, value); }
  8. }
  9.  
  10. /// <summary>
  11. /// 是否播放动画
  12. /// </summary>
  13. public static readonly DependencyProperty PlayProperty =
  14. DependencyProperty.Register("Play", typeof(bool), typeof(Radar), new PropertyMetadata(false));

xaml代码( 部分)

  1. <Style.Resources>
  2. <LinearGradientBrush x:Key="radarcolor" StartPoint="0,0" EndPoint="0,1">
  3. <GradientStop Offset="0" Color="Lime" />
  4. <GradientStop Offset="0.5" Color="Transparent" />
  5. </LinearGradientBrush>
  6. </Style.Resources>
  7. <Setter Property="Template">
  8. <Setter.Value>
  9. <ControlTemplate TargetType="{x:Type local:Radar}">
  10. <Grid x:Name="grid" >
  11. <Grid.RowDefinitions>
  12. <RowDefinition Height="2*"/>
  13. <RowDefinition Height="2*"/>
  14. </Grid.RowDefinitions>
  15. <Grid.ColumnDefinitions>
  16. <ColumnDefinition Width="2*"/>
  17. <ColumnDefinition Width="2*"/>
  18. </Grid.ColumnDefinitions>
  19. <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic" ItemsSource="{TemplateBinding RadarCircle }">
  20. <ItemsControl.ItemsPanel>
  21. <ItemsPanelTemplate>
  22. <Grid IsItemsHost="True"/>
  23. </ItemsPanelTemplate>
  24. </ItemsControl.ItemsPanel>
  25. <ItemsControl.ItemTemplate>
  26. <DataTemplate>
  27. <Ellipse Width="{Binding Width}" Height="{Binding Height}" Stroke="{Binding Color}"/>
  28. </DataTemplate>
  29. </ItemsControl.ItemTemplate>
  30. </ItemsControl>
  31. <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic2" ItemsSource="{TemplateBinding RadarLine }">
  32. <ItemsControl.ItemsPanel>
  33. <ItemsPanelTemplate>
  34. <Grid IsItemsHost="True"/>
  35. </ItemsPanelTemplate>
  36. </ItemsControl.ItemsPanel>
  37. <ItemsControl.ItemTemplate>
  38. <DataTemplate>
  39. <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="{Binding Color}"/>
  40. </DataTemplate>
  41. </ItemsControl.ItemTemplate>
  42. </ItemsControl>
  43. <Ellipse Fill="{TemplateBinding RadarColor}" Grid.ColumnSpan="2" Grid.RowSpan="2" x:Name="ep" RenderTransformOrigin="0.5,0.5" Width="{TemplateBinding RadarFillWidth}" Height="{TemplateBinding RadarFillWidth}">
  44. <Ellipse.RenderTransform>
  45. <RotateTransform x:Name="rtf" />
  46. </Ellipse.RenderTransform>
  47. </Ellipse>
  48. </Grid>
  49. <ControlTemplate.Triggers>
  50. <Trigger Property="Play" Value="True">
  51. <Trigger.EnterActions>
  52. <BeginStoryboard x:Name="bs" >
  53. <Storyboard >
  54. <DoubleAnimation Storyboard.TargetName="rtf" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever"/>
  55. </Storyboard>
  56. </BeginStoryboard>
  57. </Trigger.EnterActions>
  58. </Trigger>
  59. <Trigger Property="Play" Value="False">
  60. <Trigger.EnterActions>
  61. <RemoveStoryboard BeginStoryboardName="bs"/>
  62. </Trigger.EnterActions>
  63. </Trigger>
  64. </ControlTemplate.Triggers>
  65. </ControlTemplate>
  66. </Setter.Value>

效果

那么剩下就是扫描点的操作。

因为我们的控件是继承ItemsControl,我们到现在还没有利用ItemsSource这个属性。

所以我们要制作一个子控件来呈现扫描点。

由于子控件较为简单,只不过是一个圆而已。我们就让子控件继承Control就好了。

一切从简,我们不弄布局这一套了,直接在父控件中使用Canvas面板,子控件增加属性Left,Top这两个依赖属性。

重点说一下,子控件中存在一个linscar的方法,是为了将点如果在雷达外侧时,按照同角度缩放到最外层的方法。就是通过半径重新计算一边极坐标。

  1.     /// <summary>
  2. /// 线性缩放
  3. /// </summary>
  4. /// <param name="size">半径</param>
  5. internal void LineScar(double size)
  6. {
  7. var midpoint = new Vector(size, size);
  8. var vp = new Vector(Left, Top);
  9. var sub = vp - midpoint;
  10. var angle = Vector.AngleBetween(sub, new Vector(size, 1));
  11. angle = angle > 0 ? angle : angle + 360;
  12. //距离大于半径,根据半径重新绘制
  13. if (sub.Length >= size)
  14. {
  15. Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
  16. Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
  17. }
  18. }

那么在父项中如何摆放呢?

我们刚才说父项使用canvas绘图,所以我们在radar中修改itempanel的面板属性,下面代码存在于父项xaml

  1. <Setter Property="ItemsPanel">
  2. <Setter.Value>
  3. <ItemsPanelTemplate>
  4. <Canvas IsItemsHost="True"/>
  5. </ItemsPanelTemplate>
  6. </Setter.Value>
  7. </Setter>

子项代码如下,比较少就贴了

xaml代码

  1. <Style TargetType="local:RadarItem">
  2. <Setter Property="VerticalAlignment" Value="Top" />
  3. <Setter Property="HorizontalAlignment" Value="Left" />
  4. <Setter Property="Padding" Value="0" />
  5. <Setter Property="Margin" Value="0" />
  6. <Setter Property="Canvas.Top" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Top}" />
  7. <Setter Property="Canvas.Left" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Left}" />
  8. <Setter Property="Template">
  9. <Setter.Value>
  10. <ControlTemplate TargetType="local:RadarItem">
  11. <Border >
  12. <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Color}" />
  13. </Border>
  14. </ControlTemplate>
  15. </Setter.Value>
  16. </Setter>
  17. </Style>

radarItem

  1.   /// <summary>
  2. /// 雷达子项
  3. /// </summary>
  4. public class RadarItem : Control
  5. {
  6.  
  7. static RadarItem()
  8. {
  9. DefaultStyleKeyProperty.OverrideMetadata(typeof(RadarItem), new FrameworkPropertyMetadata(typeof(RadarItem)));
  10. }
  11. public RadarItem()
  12. {
  13.  
  14. }
  15.  
  16. /// <summary>
  17. /// 转弧度
  18. /// </summary>
  19. /// <param name="val">角度</param>
  20. /// <returns>弧度制</returns>
  21. double Rad(double val)
  22. {
  23. return val * Math.PI / 180;
  24. }
  25. /// <summary>
  26. /// 线性缩放
  27. /// </summary>
  28. /// <param name="size">半径</param>
  29. internal void LineScar(double size)
  30. {
  31. var midpoint = new Vector(size, size);
  32. var vp = new Vector(Left, Top);
  33. var sub = vp - midpoint;
  34. var angle = Vector.AngleBetween(sub, new Vector(size, 1));
  35. angle = angle > 0 ? angle : angle + 360;
  36. //距离大于半径,根据半径重新绘制
  37. if (sub.Length >= size)
  38. {
  39. Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
  40. Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
  41. }
  42. }
  43.  
  44. /// <summary>
  45. /// 顶部距离,用canvas.top绘制
  46. /// </summary>
  47. public double Top
  48. {
  49. get { return (double)GetValue(TopProperty); }
  50. set { SetValue(TopProperty, value); }
  51. }
  52.  
  53. /// <summary>
  54. /// 顶部距离,用canvas.top绘制
  55. /// </summary>
  56. public static readonly DependencyProperty TopProperty =
  57. DependencyProperty.Register("Top", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));
  58.  
  59. /// <summary>
  60. /// 左侧距离,用于canvas.left绘制
  61. /// </summary>
  62. public double Left
  63. {
  64. get { return (double)GetValue(LeftProperty); }
  65. set { SetValue(LeftProperty, value); }
  66. }
  67.  
  68. /// <summary>
  69. /// 左侧距离,用于canvas.left绘制
  70. /// </summary>
  71. public static readonly DependencyProperty LeftProperty =
  72. DependencyProperty.Register("Left", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));
  73.  
  74. /// <summary>
  75. /// 填充颜色
  76. /// </summary>
  77. public Brush Color
  78. {
  79. get { return (Brush)GetValue(ColorProperty); }
  80. set { SetValue(ColorProperty, value); }
  81. }
  82.  
  83. /// <summary>
  84. /// 填充颜色
  85. /// </summary>
  86. public static readonly DependencyProperty ColorProperty =
  87. DependencyProperty.Register("Color", typeof(Brush), typeof(RadarItem), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
  88. }

于是乎我们就得到了一个雷达扫描图

WPF 制作雷达扫描图的更多相关文章

  1. Qt自定义控件之仪表盘3--雷达扫描图

    1.设计思想 雷达扫描图,在影视作品中见到较多,比如飞机雷达.舰艇雷达,有一个扫描线转圈代表雷达一周旋转或一个批次的收发,发现目标就在表盘上标记位置.和汽车仪表盘类似,汽车仪表盘有底盘背景图.同圆.刻 ...

  2. 在Excel中制作雷达图

    雷达图的作用 雷达图是专门用来进行多指标体系比较分析的专业图表.从雷达图中可以看出指标的实际值与参照值的偏离程度,从而为分析者提供有益的信息.雷达图一般用于成绩展示.效果对比量化.多维数据对比等等,只 ...

  3. python批量制作雷达图

    老板要画雷达图,但是数据好多组怎么办?不能一个一个点excel去画吧,那么可以利用python进行批量制作,得到样式如下: 首先制作一个演示的excel,评分为excel随机数生成: 1 =INT(( ...

  4. WPF制作的小型笔记本

    WPF制作的小型笔记本-仿有道云笔记 楼主所在的公司不允许下载外部资源, 不允许私自安装应用程序, 平时记录东西都是用记事本,时间久了很难找到以前记的东西. 平时在家都用有道笔记, 因此就模仿着做了一 ...

  5. WPF制作表示透明区域的马赛克画刷

    最近在用WPF制作一款软件,需要像ps一样表示透明区域,于是制作了一个马赛克背景的style.实现比较简单,那么过程和思路就不表了,直接上代码 <DrawingBrush TileMode=&q ...

  6. WPF模拟雷达界面效果图

    原文:WPF模拟雷达界面效果图 iPad塔防的防守兵的效果很炫,2个小时用WPF模拟了一个. 效果图: 关键代码: <Grid> <Grid.Background> <I ...

  7. WPF制作的小时钟

    原文:WPF制作的小时钟 周末无事, 看到WEB QQ上的小时钟挺可爱的, 于是寻思着用WPF模仿着做一个. 先看下WEB QQ的图: 打开VS, 开始动工. 建立好项目后, 面对一个空荡荡的页面, ...

  8. WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事!

    原文:WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事! 这是效果: XAML代码:<Viewbox Width="723.955078" Height=&q ...

  9. WPF制作的党旗

    原文:WPF制作的党旗 --------------------------------------------------------------------------------引用或转载时请保 ...

随机推荐

  1. java-与文件相关

    java.nio.file 表示non-blocking 非阻塞io(输入和输出) 一个 Path 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象 java.nio.file ...

  2. three模型高亮外发光效果

    页面效果如下: vue3+three完整代码如下: <template> </template> <script setup> import * as THREE ...

  3. SpringAop实现原理及代理模式

    Spring Aop的原理 Spring的AOP就是通过动态代理实现的.当为某个Bean或者某些Bean配置切面时,Spring会为其创建代理对象,当调用该对象的某个方法时,实际是调用生成的代理类的对 ...

  4. 浏览器中唤起native app || 跳转到应用商城下载

    前段时间遇到一个小需求:要求在分享出来的h5页面中,有一个立即打开的按钮,如果本地安装了我们的app,那么点击就直接唤起本地app,如果没有安装,则跳转到下载. 因为从来没有做过这个需求,因此这注定是 ...

  5. WebView的一些简单用法

    一直想写一个关于 WebView 控件的 一些简单运用,都没什么时间,这次也是挤出时间写的,里面的一些基础知识就等有时间再更新讲解一下,今天就先把项目出来做一些简单介绍,过多的内容可以看我的源码,都传 ...

  6. Slog71_选取、上传和显示本地图片GET !(微信小程序之云开发-全栈时代3)

    ArthurSlog SLog-71 Year·1 Guangzhou·China Sep 12th 2018 ArthurSlog Page GitHub NPM Package Page 掘金主页 ...

  7. 小程序web开发框架-weweb介绍

    weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web单面应用.如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中.在小程序大行其道的今天,它可以让你的小程序代码得到最大 ...

  8. python---概述

    python的主要应用领域 云计算:云计算的最火的语言,典型应用OpenStack. web开发:众多优秀的web框架,典型地有Django,众多大型网站也是python开发,比如YouTube.豆瓣 ...

  9. Leetcode216/39/40/77之回溯解决经典组合问题

    Leetcode216-组合总和三 找出所有相加之和为 n 的 k 个数的组合,且满足下列条件: 只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 .该列表不能包含相同的组合两 ...

  10. Linux shell中2>&1的含义解释

    https://blog.csdn.net/zhaominpro/article/details/82630528