WPF 製作雷達掃描圖

来源:https://www.cnblogs.com/T-ARF/archive/2022/05/10/16253121.html
-Advertisement-
Play Games

實現一個雷達掃描圖。 源代碼在TK_King/雷達 (gitee.com),自行下載就好了 製作思路 繪製圓形(或者稱之輪) 繪製分割線 繪製掃描範圍 添加掃描點 具體實現 首先我們使用自定義的控制項。你可以使用vs自動添加,也可以手動創建類。註意手動創建時要創建Themes/Generic.xaml ...


 

實現一個雷達掃描圖。

源代碼在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

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

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

對應泛型類可以參考源代碼,基本元素就是綁定ellipse的參數

 <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic"  ItemsSource="{TemplateBinding RadarCircle }">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Grid IsItemsHost="True"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Ellipse Width="{Binding  Width}" Height="{Binding Height}"  Stroke="{Binding Color}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
    </ItemsControl>

 

哇啦,圖像就出來了。

 

 

同理,我們創建分割線也是同樣的過程。

對於分割線的切割演算法,我們使用圓上點的坐標可以通過( 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;

 

依賴屬性

     /// <summary>
        /// 雷達圖的分割線,目前固定為6,可以自行修改
        /// </summary>
        public FreezableCollection<RadarLineSize> RadarLine
        {
            get { return (FreezableCollection<RadarLineSize>)GetValue(RadarLineProperty); }
            set { SetValue(RadarLineProperty, value); }
        }

        /// <summary>
        /// 雷達圖的分割線,目前固定為6,可以自行修改
        /// </summary>
        public static readonly DependencyProperty RadarLineProperty =
            DependencyProperty.Register("RadarLine", typeof(FreezableCollection<RadarLineSize>), typeof(Radar));

xaml代碼

             <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2"  VerticalAlignment="Center" HorizontalAlignment="Center"  x:Name="ic2"   ItemsSource="{TemplateBinding RadarLine }">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Grid IsItemsHost="True"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}"  Stroke="{Binding Color}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>

 

 

 

 

 下一步就是扇形掃描了。

我們使用一個完整的圓,將其內部顏色填充為線性刷就可以得到一個效果不錯的掃描了。

     /// <summary>
        /// 雷達掃描的顏色
        /// </summary>
        public Brush RadarColor
        {
            get { return (Brush)GetValue(RadarColorProperty); }
            set { SetValue(RadarColorProperty, value); }
        }

        /// <summary>
        /// 雷達掃描的顏色
        /// </summary>
        public static readonly DependencyProperty RadarColorProperty =
            DependencyProperty.Register("RadarColor", typeof(Brush), typeof(Radar));

 

為了更好的定義這個圓,我們將radar的template使用grid面板等距分成四個區域(其實沒啥用,主要是為了扇形掃描時做圓心選擇的line,也可以不分成四個)。

在考慮動畫,只需要做圓形360的選擇就可以了。為了更好應用,我們創一個paly的依賴屬性來播放動畫。

     /// <summary>
        /// 是否播放動畫
        /// </summary>
        public bool Play
        {
            get { return (bool)GetValue(PlayProperty); }
            set { SetValue(PlayProperty, value); }
        }

        /// <summary>
        /// 是否播放動畫
        /// </summary>
        public static readonly DependencyProperty PlayProperty =
            DependencyProperty.Register("Play", typeof(bool), typeof(Radar), new PropertyMetadata(false));

 

xaml代碼( 部分)

 <Style.Resources>
            <LinearGradientBrush x:Key="radarcolor" StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0" Color="Lime" />
                <GradientStop Offset="0.5" Color="Transparent" />
            </LinearGradientBrush>
        </Style.Resources>
  <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:Radar}">
                    <Grid x:Name="grid"   >
                        <Grid.RowDefinitions>
                            <RowDefinition Height="2*"/>
                            <RowDefinition Height="2*"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="2*"/>
                            <ColumnDefinition Width="2*"/>
                        </Grid.ColumnDefinitions>
                        <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic"  ItemsSource="{TemplateBinding RadarCircle }">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Grid IsItemsHost="True"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Ellipse Width="{Binding  Width}" Height="{Binding Height}"  Stroke="{Binding Color}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                        <ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2"  VerticalAlignment="Center" HorizontalAlignment="Center"  x:Name="ic2"   ItemsSource="{TemplateBinding RadarLine }">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Grid IsItemsHost="True"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}"  Stroke="{Binding Color}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                        <Ellipse Fill="{TemplateBinding RadarColor}"   Grid.ColumnSpan="2" Grid.RowSpan="2"  x:Name="ep" RenderTransformOrigin="0.5,0.5" Width="{TemplateBinding RadarFillWidth}" Height="{TemplateBinding RadarFillWidth}">
                            <Ellipse.RenderTransform>
                                <RotateTransform x:Name="rtf" />
                            </Ellipse.RenderTransform>
                        </Ellipse>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Play" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard  x:Name="bs" >
                                    <Storyboard >
                                        <DoubleAnimation Storyboard.TargetName="rtf" Storyboard.TargetProperty="Angle"   From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                        </Trigger>
                        <Trigger Property="Play" Value="False">
                            <Trigger.EnterActions>
                                <RemoveStoryboard BeginStoryboardName="bs"/>
                            </Trigger.EnterActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>

 

效果

那麼剩下就是掃描點的操作。

因為我們的控制項是繼承ItemsControl,我們到現在還沒有利用ItemsSource這個屬性。

所以我們要製作一個子控制項來呈現掃描點。

由於子控制項較為簡單,只不過是一個圓而已。我們就讓子控制項繼承Control就好了。

一切從簡,我們不弄佈局這一套了,直接在父控制項中使用Canvas面板,子控制項增加屬性Left,Top這兩個依賴屬性。

重點說一下,子控制項中存在一個linscar的方法,是為了將點如果在雷達外側時,按照同角度縮放到最外層的方法。就是通過半徑重新計算一邊極坐標。

     /// <summary>
        /// 線性縮放
        /// </summary>
        /// <param name="size">半徑</param>
        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

   <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <Canvas IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>

 

子項代碼如下,比較少就貼了

xaml代碼

    <Style TargetType="local:RadarItem">
        <Setter Property="VerticalAlignment" Value="Top" />
        <Setter Property="HorizontalAlignment" Value="Left" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="Margin" Value="0" />
        <Setter Property="Canvas.Top" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Top}" />
        <Setter Property="Canvas.Left" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Left}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:RadarItem">
                    <Border  >
                        <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Color}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

 

radarItem

  /// <summary>
    /// 雷達子項 
    /// </summary>
    public class RadarItem : Control
    {

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

        }

        /// <summary>
        /// 轉弧度
        /// </summary>
        /// <param name="val">角度</param>
        /// <returns>弧度制</returns>
        double Rad(double val)
        {
            return val * Math.PI / 180;
        }
        /// <summary>
        /// 線性縮放
        /// </summary>
        /// <param name="size">半徑</param>
        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;
            }
        }

        /// <summary>
        /// 頂部距離,用canvas.top繪製
        /// </summary>
        public double Top
        {
            get { return (double)GetValue(TopProperty); }
            set { SetValue(TopProperty, value); }
        }

        /// <summary>
        /// 頂部距離,用canvas.top繪製
        /// </summary>
        public static readonly DependencyProperty TopProperty =
            DependencyProperty.Register("Top", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));


        /// <summary>
        /// 左側距離,用於canvas.left繪製
        /// </summary>
        public double Left
        {
            get { return (double)GetValue(LeftProperty); }
            set { SetValue(LeftProperty, value); }
        }

        /// <summary>
        /// 左側距離,用於canvas.left繪製
        /// </summary>
        public static readonly DependencyProperty LeftProperty =
            DependencyProperty.Register("Left", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0));


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

        /// <summary>
        /// 填充顏色
        /// </summary>
        public static readonly DependencyProperty ColorProperty =
            DependencyProperty.Register("Color", typeof(Brush), typeof(RadarItem), new PropertyMetadata(new	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在C/C++中有個叫指針的玩意存在感極其強烈,而說到指針又不得不提到記憶體管理。現在時不時能聽到一些朋友說指針很難,實際上說的是記憶體操作和管理方面的難。(這篇筆記咱也會結合自己的理解簡述一些相關的記憶體知識) 最近在寫C程式使用指針的時候遇到了幾個讓我印象深刻的地方,這裡記錄一下,以便今後回顧。 “經一 ...
  • 面向對象和麵向過程的區別 區別簡述 面向過程(Procedure Oriented):以過程為核心,強調**事件的流程、順序,**如:C語言。 面向對象(Object Oriented):以對象為核心,強調**事件的角色、主體,**如:C++、Java。 區別 1.思路不同 2.特點不同 3.優勢不 ...
  • 現象: 最近將pyinsatller升級到最新的 Version: 5.0.1版本後(之前一直用的是3.5版本同樣方法未遇到問題,今次更新到最新版本後5.0.1後打包就遇到問題,具體是這中間哪個版本開始有變化也不清楚了,也不去追究,凡是在新版本中遇到問題就在新版本中解決),詳細現象及解決辦法如下: ...
  • 昨天突發奇想想來玩一玩,然後安裝了一下午才成功,基本所有該踩的坑都踩了,但當時沒截圖,現在靠著記憶寫一下。 官網鏈接:https://www.mongodb.com/try/download/community。 需要註意的是:超過次數就必須要登錄才能下載。(證明我真的試過很多次) 1.最開始出現的 ...
  • 今天某個項目的數據有些問題,需要查詢日誌看看具體的情況 結果在執行 cat ***.log |grep "關鍵字" 命令後包如下錯誤: grep: memory exhausted 思路1: 既然提示 記憶體問題,是不是日誌文件太大了, 用 du -sh * 命令查看 後文件也就 300M 思路2: ...
  • 下載&安裝Cmake 進入下載頁面 Download | CMake 選擇安裝包版本 打開安裝包,下一步之後選擇添加path 選擇完安裝文件夾開始安裝 下載&配置OpenCV 進入下載頁面 Releases - OpenCV 選擇版本下載(我下的是 Sources,Windows版是已經構建好的,不 ...
  • 多線程筆記(二) 1. Synchronized 和 Lock 的區別 synchronized是Java的關鍵字,是 JVM 層面的內置功能和實現。 Lock是一個介面,是代碼層面的實現 synchronized可以隱式的獲取,釋放鎖 lock是顯式的獲取,釋放鎖 synchronized在發生異 ...
  • 來源:編碼磚家 鏈接:cnblogs.com/xiaoyangjia/p/11267191.html 背景 最近頻繁出現慢SQL告警,執行時間最長的竟然高達5分鐘。導出日誌後分析,主要原因竟然是沒有命中索引和沒有分頁處理 。 其實這是非常低級的錯誤,我不禁後背一涼,團隊成員的技術水平亟待提高啊。改造 ...
一周排行
    -Advertisement-
    Play Games
  • 分組和樹形結構是不一樣的。 樹形結構是以遞歸形式存在。分組是以鍵值對存在的形式,類似於GroupBy這樣的形式。 舉個例子 ID NAME SEX Class 1 張三 男 1 2 李四 女 2 3 王二 男 1 當以Sex為分組依據時則是 Key Value 男 1 張三 男 1 3 王二 男 1 ...
  • NetCore中將SQLServer資料庫備份為Sql腳本 描述: 最近寫項目收到了一個需求, 就是將SQL Server資料庫備份為Sql腳本, 如果是My Sql之類的還好說, 但是在網上搜了一大堆, 全是教你怎麼操作SSMS的, 就很d疼! 解決方案: 通過各種查找資料, 還有一些老哥的幫助, ...
  • 我的Notion Clowd.Squirrel Squirrel.Windows 是一組工具和適用於.Net的庫,用於管理 Desktop Windows 應用程式的安裝和更新。 Squirrel.Windows 對 Windows 應用程式的實現語言沒有任何要求,甚至無需服務端即可完成增量更新。 ...
  • 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblog ...
  • 1. Netty源碼研究筆記(3)——Channel系列 依舊是通過先縱向再橫向的研究方法,在開篇中,我們發現不管是Sever還是Client,最終的啟動是通過調用channel的對應方法來完成的,而這個動作實際在channel綁定的eventLoop中執行。 接下來,我們繼續EchoSever、E ...
  • 大家好,今天給大家介紹一款輕量、快速、穩定可編排的組件式規則引擎框架LiteFlow。 一、LiteFlow的介紹 LiteFlow官方網站和代碼倉庫地址 官方網站:https://yomahub.com/liteflow Gitee托管倉庫:https://gitee.com/dromara/li ...
  • 我使用Spring AOP實現了用戶操作日誌功能 今天答辯完了,復盤了一下系統,發現還是有一些東西值得拿出來和大家分享一下。 需求分析 系統需要對用戶的操作進行記錄,方便未來溯源 首先想到的就是在每個方法中,去實現記錄的邏輯,但是這樣做肯定是不現實的,首先工作量大,其次違背了軟體工程設計原則(開閉原 ...
  • 《零基礎學Java》 繪製幾何圖形 Java可以分別使用 Graphics 和 Graphics2D 繪製圖形,Graphics類 使用不同的方法繪製不同的圖形(drawLine()方法可f以繪製線、drawRect()方法用於繪製矩形、drawOval()方法用於繪製橢圓形)。 Graphics類 ...
  • 本期教程人臉識別第三方平臺為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。 ...
  • 很多人都喜歡使用黑色的主題樣式,包括我自己,使用了差不多三年的黑色主題,但是個人覺得在進行視窗轉換的時候很廢眼睛。 比如IDEA是全黑的,然後需要看PDF或者WORD又變成白色的了,這樣來回切換導致眼睛很累,畢竟現在網頁以及大部分軟體的界面都是白色的。那麼還是老老實實的使用原來比較順眼的模式吧。 1 ...