WPF实现雷达扫描图的绘制详解

前言

实现一个雷达扫描图。

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

制作思路

  • 绘制圆形(或者称之轮)
  • 绘制分割线
  • 绘制扫描范围
  • 添加扫描点

具体实现

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

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

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

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

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

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

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

所以,我们构造一个集合依赖属性。关于集合依赖属性我们可以参考MSDN官方文档

/// 
        /// 每圈的大小
        /// 
        public FreezableCollection RadarCircle
        {
            get { return (FreezableCollection)GetValue(RadarCircleProperty); }
            set { SetValue(RadarCircleProperty, value); }
        }

        /// 
        /// 每圈的大小
        /// 
        public static readonly DependencyProperty RadarCircleProperty =
            DependencyProperty.Register("RadarCircle", typeof(FreezableCollection), typeof(Radar), new PropertyMetadata(new PropertyChangedCallback(OnRadarCircelValueChanged)));

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


                            
                                
                                    
                                
                            
                            
                                
                                    
                                
                            
    

哇啦,图像就出来了。

WPF实现雷达扫描图的绘制详解_第1张图片

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

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

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

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

var angle = 180.0 / 6;
            circlesize = size.Height > size.Width ? size.Width : size.Height;
            RadarFillWidth = circlesize;
            var midx = circlesize / 2.0;
            var midy = circlesize / 2.0;
            circlesize = circlesize / 2;
            RadarRadius = circlesize;
            //默认为6个
            for (int i = 0; i < 6; i++)
            {
                var baseangel = angle * i;
                var l1 = new Point(midx + circlesize * Math.Cos(Rad(baseangel)), midy - circlesize * Math.Sin(Rad(baseangel)));
                var half = baseangel + 180;
                var l2 = new Point(midx + circlesize * Math.Cos(Rad(half)), midy - circlesize * Math.Sin(Rad(half)));
                RadarLineSize radarLine = new RadarLineSize();
                radarLine.Start = l1;
                radarLine.End = l2;
                radarLine.Color = RadarLineColor;
                RadarLine.Add(radarLine);
            }
            return size;

依赖属性

/// 
        /// 雷达图的分割线,目前固定为6,可以自行修改
        /// 
        public FreezableCollection RadarLine
        {
            get { return (FreezableCollection)GetValue(RadarLineProperty); }
            set { SetValue(RadarLineProperty, value); }
        }

        /// 
        /// 雷达图的分割线,目前固定为6,可以自行修改
        /// 
        public static readonly DependencyProperty RadarLineProperty =
            DependencyProperty.Register("RadarLine", typeof(FreezableCollection), typeof(Radar));

xaml代码


                            
                                
                                    
                                
                            
                            
                                
                                    
                                
                            
                        

WPF实现雷达扫描图的绘制详解_第2张图片

下一步就是扇形扫描了。

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

/// 
        /// 雷达扫描的颜色
        /// 
        public Brush RadarColor
        {
            get { return (Brush)GetValue(RadarColorProperty); }
            set { SetValue(RadarColorProperty, value); }
        }

        /// 
        /// 雷达扫描的颜色
        /// 
        public static readonly DependencyProperty RadarColorProperty =
            DependencyProperty.Register("RadarColor", typeof(Brush), typeof(Radar));

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

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

/// 
        /// 是否播放动画
        /// 
        public bool Play
        {
            get { return (bool)GetValue(PlayProperty); }
            set { SetValue(PlayProperty, value); }
        }

        /// 
        /// 是否播放动画
        /// 
        public static readonly DependencyProperty PlayProperty =
            DependencyProperty.Register("Play", typeof(bool), typeof(Radar), new PropertyMetadata(false));

xaml代码( 部分)


            
                
                
            
        
  
            
                
                    
                        
                            
                            
                        
                        
                            
                            
                        
                        
                            
                                
                                    
                                
                            
                            
                                
                                    
                                
                            
                        
                        
                            
                                
                                    
                                
                            
                            
                                
                                    
                                
                            
                        
                        
                            
                                
                            
                        
                    
                    
                        
                            
                                
                                    
                                        
                                    
                                
                            
                        
                        
                            
                                
                            
                        
                    
                
            

效果

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

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

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

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

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

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

/// 
        /// 线性缩放
        /// 
        /// 半径
        internal void LineScar(double size)
        {
            var midpoint = new Vector(size, size);
            var vp = new Vector(Left, Top);
            var sub = vp - midpoint;
            var angle = Vector.AngleBetween(sub, new Vector(size, 1));
            angle = angle > 0 ? angle : angle + 360;
            //距离大于半径,根据半径重新绘制
            if (sub.Length >= size)
            {
                Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
                Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
            }
        }

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

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


            
                
                    
                
            
        

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

xaml代码

radarItem

/// 
    /// 雷达子项 
    /// 
    public class RadarItem : Control
    {

        static RadarItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RadarItem), new FrameworkPropertyMetadata(typeof(RadarItem)));
        }
        public RadarItem()
        {

        }

        /// 
        /// 转弧度
        /// 
        /// 角度
        /// 弧度制
        double Rad(double val)
        {
            return val * Math.PI / 180;
        }
        /// 
        /// 线性缩放
        /// 
        /// 半径
        internal void LineScar(double size)
        {
            var midpoint = new Vector(size, size);
            var vp = new Vector(Left, Top);
            var sub = vp - midpoint;
            var angle = Vector.AngleBetween(sub, new Vector(size, 1));
            angle = angle > 0 ? angle : angle + 360;
            //距离大于半径,根据半径重新绘制
            if (sub.Length >= size)
            {
                Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
                Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
            }
        }

        /// 
        /// 顶部距离,用canvas.top绘制
        /// 
        public double Top
        {
            get { return (double)GetValue(TopProperty); }
            set { SetValue(TopProperty, value); }
        }

        /// 
        /// 顶部距离,用canvas.top绘制
        /// 
        public static readonly DependencyProperty TopProperty =
            DependencyProperty.Register("Top", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));


        /// 
        /// 左侧距离,用于canvas.left绘制
        /// 
        public double Left
        {
            get { return (double)GetValue(LeftProperty); }
            set { SetValue(LeftProperty, value); }
        }

        /// 
        /// 左侧距离,用于canvas.left绘制
        /// 
        public static readonly DependencyProperty LeftProperty =
            DependencyProperty.Register("Left", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));


        /// 
        /// 填充颜色
        /// 
        public Brush Color
        {
            get { return (Brush)GetValue(ColorProperty); }
            set { SetValue(ColorProperty, value); }
        }

        /// 
        /// 填充颜色
        /// 
        public static readonly DependencyProperty ColorProperty =
            DependencyProperty.Register("Color", typeof(Brush), typeof(RadarItem), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
    }

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

以上就是WPF实现雷达扫描图的绘制详解的详细内容,更多关于WPF雷达扫描图的资料请关注脚本之家其它相关文章!

你可能感兴趣的:(WPF实现雷达扫描图的绘制详解)