WPF實現(xiàn)雷達掃描圖的繪制詳解
前言

實現(xiàn)一個雷達掃描圖。
源代碼在TK_King/雷達 (gitee.com),自行下載就好了
制作思路
- 繪制圓形(或者稱之輪)
- 繪制分割線
- 繪制掃描范圍
- 添加掃描點
具體實現(xiàn)
首先我們使用自定義的控件。你可以使用vs自動添加,也可以手動創(chuàng)建類。注意手動創(chuàng)建時要創(chuàng)建Themes/Generic.xaml的文件路徑哦。
控件繼承自itemscontrol,取名叫做Radar。
我們第一步思考如何實現(xiàn)圓形或者輪,特別是等距的輪。
我們可以使用簡單的itemscontrol的WPF控件,通過自定義ItemTemplate就可以簡單的創(chuàng)建了。
因為要顯示圓,所以使用Ellipse是最簡單的事情。
又因為要在同一個區(qū)域內(nèi),顯示同心圓,我們將面板改為Grid,利用疊加的特性去構(gòu)造同心圓。
既然我們用了itemscontrol 來承載圈輪,直接讓這個圈可自定義呢?
所以,我們構(gòu)造一個集合依賴屬性。關(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)建分割線也是同樣的過程。
對于分割線的切割算法,我們使用圓上點的坐標可以通過( rcos,rsin)=》(x,y) ,也就是極坐標。
關(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;
//默認為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>
下一步就是扇形掃描了。
我們使用一個完整的圓,將其內(nèi)部顏色填充為線性刷就可以得到一個效果不錯的掃描了。
/// <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面板等距分成四個區(qū)域(其實沒啥用,主要是為了扇形掃描時做圓心選擇的line,也可以不分成四個)。
在考慮動畫,只需要做圓形360的選擇就可以了。為了更好應(yīng)用,我們創(chuàng)一個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,我們到現(xiàn)在還沒有利用ItemsSource這個屬性。
所以我們要制作一個子控件來呈現(xiàn)掃描點。
由于子控件較為簡單,只不過是一個圓而已。我們就讓子控件繼承Control就好了。
一切從簡,我們不弄布局這一套了,直接在父控件中使用Canvas面板,子控件增加屬性Left,Top這兩個依賴屬性。
重點說一下,子控件中存在一個linscar的方法,是為了將點如果在雷達外側(cè)時,按照同角度縮放到最外層的方法。就是通過半徑重新計算一邊極坐標。
/// <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;
}
}那么在父項中如何擺放呢?
我們剛才說父項使用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>
/// 轉(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)));
}于是乎我們就得到了一個雷達掃描圖

以上就是WPF實現(xiàn)雷達掃描圖的繪制詳解的詳細內(nèi)容,更多關(guān)于WPF雷達掃描圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何將asp.net core程序部署到Linux服務(wù)器
這篇文章主要介紹了將asp.net core程序部署到Linux服務(wù)器上的詳細過程,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09
深入.net調(diào)用webservice的總結(jié)分析
本篇文章是對.net調(diào)用webservice進行了詳細的總結(jié)與分析,需要的朋友參考下2013-05-05
asp.net中引用同一個項目中的類庫 避免goToDefinition時不能到達真正的定義類
asp.net中引用同一個項目中的類庫 避免 goToDefinition時不能到達真正的定義類2011-10-10
基于.Net?Core認證授權(quán)方案之JwtBearer認證
這篇文章介紹了基于.Net?Core認證授權(quán)方案之JwtBearer認證,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
ASP.NET?MVC使用Knockout獲取數(shù)組元素索引的2種方法
這篇文章介紹了ASP.NET?MVC使用Knockout獲取數(shù)組元素索引的2種方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08

