WPF實(shí)現(xiàn)雷達(dá)掃描圖的繪制詳解
前言
實(shí)現(xiàn)一個(gè)雷達(dá)掃描圖。
源代碼在TK_King/雷達(dá) (gitee.com),自行下載就好了
制作思路
- 繪制圓形(或者稱之輪)
- 繪制分割線
- 繪制掃描范圍
- 添加掃描點(diǎn)
具體實(shí)現(xiàn)
首先我們使用自定義的控件。你可以使用vs自動添加,也可以手動創(chuàng)建類。注意手動創(chuàng)建時(shí)要創(chuàng)建Themes/Generic.xaml的文件路徑哦。
控件繼承自itemscontrol,取名叫做Radar。
我們第一步思考如何實(shí)現(xiàn)圓形或者輪,特別是等距的輪。
我們可以使用簡單的itemscontrol的WPF控件,通過自定義ItemTemplate就可以簡單的創(chuàng)建了。
因?yàn)橐@示圓,所以使用Ellipse是最簡單的事情。
又因?yàn)橐谕粋€(gè)區(qū)域內(nèi),顯示同心圓,我們將面板改為Grid,利用疊加的特性去構(gòu)造同心圓。
既然我們用了itemscontrol 來承載圈輪,直接讓這個(gè)圈可自定義呢?
所以,我們構(gòu)造一個(gè)集合依賴屬性。關(guān)于集合依賴屬性我們可以參考MSDN官方文檔
/// <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)));
對應(yīng)泛型類可以參考源代碼,基本元素就是綁定ellipse的參數(shù)
<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>
哇啦,圖像就出來了。
同理,我們創(chuàng)建分割線也是同樣的過程。
對于分割線的切割算法,我們使用圓上點(diǎn)的坐標(biāo)可以通過( rcos,rsin)=》(x,y) ,也就是極坐標(biāo)。
關(guān)于此部分代碼是放在布局塊內(nèi)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; //默認(rèn)為6個(gè) 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> /// 雷達(dá)圖的分割線,目前固定為6,可以自行修改 /// </summary> public FreezableCollection<RadarLineSize> RadarLine { get { return (FreezableCollection<RadarLineSize>)GetValue(RadarLineProperty); } set { SetValue(RadarLineProperty, value); } } /// <summary> /// 雷達(dá)圖的分割線,目前固定為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>
下一步就是扇形掃描了。
我們使用一個(gè)完整的圓,將其內(nèi)部顏色填充為線性刷就可以得到一個(gè)效果不錯的掃描了。
/// <summary> /// 雷達(dá)掃描的顏色 /// </summary> public Brush RadarColor { get { return (Brush)GetValue(RadarColorProperty); } set { SetValue(RadarColorProperty, value); } } /// <summary> /// 雷達(dá)掃描的顏色 /// </summary> public static readonly DependencyProperty RadarColorProperty = DependencyProperty.Register("RadarColor", typeof(Brush), typeof(Radar));
為了更好的定義這個(gè)圓,我們將radar的template使用grid面板等距分成四個(gè)區(qū)域(其實(shí)沒啥用,主要是為了扇形掃描時(shí)做圓心選擇的line,也可以不分成四個(gè))。
在考慮動畫,只需要做圓形360的選擇就可以了。為了更好應(yīng)用,我們創(chuàng)一個(gè)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>
效果
那么剩下就是掃描點(diǎn)的操作。
因?yàn)槲覀兊目丶抢^承ItemsControl,我們到現(xiàn)在還沒有利用ItemsSource這個(gè)屬性。
所以我們要制作一個(gè)子控件來呈現(xiàn)掃描點(diǎn)。
由于子控件較為簡單,只不過是一個(gè)圓而已。我們就讓子控件繼承Control就好了。
一切從簡,我們不弄布局這一套了,直接在父控件中使用Canvas面板,子控件增加屬性Left,Top這兩個(gè)依賴屬性。
重點(diǎn)說一下,子控件中存在一個(gè)linscar的方法,是為了將點(diǎn)如果在雷達(dá)外側(cè)時(shí),按照同角度縮放到最外層的方法。就是通過半徑重新計(jì)算一邊極坐標(biāo)。
/// <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; //距離大于半徑,根據(jù)半徑重新繪制 if (sub.Length >= size) { Top = size - size * Math.Sin(Rad(angle)) - Width / 2; Left = size + size * Math.Cos(Rad(angle)) - Width / 2; } }
那么在父項(xiàng)中如何擺放呢?
我們剛才說父項(xiàng)使用canvas繪圖,所以我們在radar中修改itempanel的面板屬性,下面代碼存在于父項(xiàng)xaml
<Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <Canvas IsItemsHost="True"/> </ItemsPanelTemplate> </Setter.Value> </Setter>
子項(xiàng)代碼如下,比較少就貼了
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> /// 雷達(dá)子項(xiàng) /// </summary> public class RadarItem : Control { static RadarItem() { DefaultStyleKeyProperty.OverrideMetadata(typeof(RadarItem), new FrameworkPropertyMetadata(typeof(RadarItem))); } public RadarItem() { } /// <summary> /// 轉(zhuǎn)弧度 /// </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; //距離大于半徑,根據(jù)半徑重新繪制 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> /// 左側(cè)距離,用于canvas.left繪制 /// </summary> public double Left { get { return (double)GetValue(LeftProperty); } set { SetValue(LeftProperty, value); } } /// <summary> /// 左側(cè)距離,用于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 SolidColorBrush(Colors.Red))); }
于是乎我們就得到了一個(gè)雷達(dá)掃描圖
以上就是WPF實(shí)現(xiàn)雷達(dá)掃描圖的繪制詳解的詳細(xì)內(nèi)容,更多關(guān)于WPF雷達(dá)掃描圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
.NET Core中創(chuàng)建和使用NuGet包的示例代碼
這篇文章主要介紹了.NET Core中創(chuàng)建和使用NuGet包的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Entity Framework Core對Web項(xiàng)目生成數(shù)據(jù)庫表
這篇文章介紹了Entity Framework Core對Web項(xiàng)目生成數(shù)據(jù)庫表的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03ASP.NET之Excel下載模板、導(dǎo)入、導(dǎo)出操作
這篇文章主要介紹了ASP.NET之Excel下載模板、導(dǎo)入、導(dǎo)出操作的相關(guān)資料,需要的朋友可以參考下2016-07-07寫一個(gè)含數(shù)字,拼音,漢字的驗(yàn)證碼生成類
本文和大家分享的是一個(gè)集成1:小寫拼音;2:大寫拼音;3:數(shù)字;4:漢字的驗(yàn)證碼生成類。本章例子也會有一個(gè)mvc使用驗(yàn)證碼校驗(yàn)的場景。具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01在Apache環(huán)境下成功的運(yùn)行ASP.NET的注意事項(xiàng)
在Apache環(huán)境下成功的運(yùn)行ASP.NET的注意事項(xiàng)...2007-08-08ASP.NET?Core基于滑動窗口實(shí)現(xiàn)限流控制
這篇文章主要介紹了ASP.NET?Core基于滑動窗口實(shí)現(xiàn)限流控制,?AspNetCoreRateLimit是目前ASP.NET?Core下最常用的限流解決方案,下面詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-03-03