先看一下显示效果:
需要注意的地方有以下几点:
- 表盘的刻度分部,长刻度和短刻度显示。
- 在数值80W时,需要更改刻度盘的颜色渐变。
- 在数值80W时,更改库容总数背景的显示,也是颜色渐变。刻度盘控件属性定义:
刻度盘的定义:
using Microsoft.Expression.Shapes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Dashboard_Demo
{
/// <summary>
/// 刻度盘控件
/// </summary>
/// <remarks>add by zhidanfeng 2017.2.19</remarks>
[TemplatePart(Name = "PART_IncreaseCircle", Type = typeof(Arc))]
public class Dashboard : Control
{
private Arc PART_IncreaseCircle;
/// <summary>
/// 保存角度变化前的角度值(用于动画)
/// </summary>
private double OldAngle;
#region Constructors
static Dashboard()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Dashboard), new FrameworkPropertyMetadata(typeof(Dashboard)));
}
#endregion
#region 依赖属性
#region Angle 刻度盘起始角度
/// <summary>
/// 刻度盘起始角度依赖属性
/// </summary>
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register(
"StartAngle",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(0d));
/// <summary>
/// 刻度盘起始角度
/// </summary>
public double StartAngle
{
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
#endregion
#region Angle 刻度盘结束角度依赖属性
/// <summary>
/// 刻度盘结束角度依赖属性
/// </summary>
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register(
"EndAngle",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(0d));
/// <summary>
/// 刻度盘结束角度依赖属性
/// </summary>
public double EndAngle
{
get { return (double)GetValue(EndAngleProperty); }
set
{
SetValue(EndAngleProperty, value);
}
}
#endregion
#region Minimum 最小值
/// <summary>
/// 最小值依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(
"Minimum",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(0.0));
/// <summary>
/// 获取或设置最小值.
/// </summary>
/// <value>最小值.</value>
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
#endregion
#region Maximum 最大值
/// <summary>
/// 最大值依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register(
"Maximum",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(100.0));
/// <summary>
/// 获取或设置最大值.
/// </summary>
/// <value>最大值.</value>
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
#endregion
#region Value 当前值
/// <summary>
/// 最大值依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnValuePropertyChanged)));
/// <summary>
/// 获取或设置当前值
/// </summary>
public double Value
{
get
{
if (PART_IncreaseCircle == null)
return (double)GetValue(ValueProperty);
if ((double)GetValue(ValueProperty) > 800000d)
{
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 0);
brush.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#f0b046"), 0));
brush.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#e83a2d"), 1));
PART_IncreaseCircle.Stroke = brush;
}
else
{
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 0);
brush.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#1ccabd"), 0));
brush.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#458ffc"), 1));
PART_IncreaseCircle.Stroke = brush;
}
return (double)GetValue(ValueProperty);
}
set { SetValue(ValueProperty, value); }
}
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Dashboard dashboard = d as Dashboard;
dashboard.OldAngle = dashboard.Angle;
dashboard.SetAngle();
dashboard.TransformAngle();
}
#endregion
#region LongTickCount 长刻度个数
public static readonly DependencyProperty LongTickCountProperty =
DependencyProperty.Register(
"LongTickCount",
typeof(int),
typeof(Dashboard),
new PropertyMetadata(5));
/// <summary>
/// 获取或设置长刻度个数,用于设置刻度盘显示几个长刻度
/// </summary>
public int LongTickCount
{
get { return (int)GetValue(LongTickCountProperty); }
set { SetValue(LongTickCountProperty, value); }
}
#endregion
#region ShortTickCount 短刻度个数
public static readonly DependencyProperty ShortTickCountProperty =
DependencyProperty.Register(
"ShortTickCount",
typeof(int),
typeof(Dashboard),
new PropertyMetadata(3));
/// <summary>
/// 获取或设置两个长刻度之间的短刻度的个数
/// </summary>
public int ShortTickCount
{
get { return (int)GetValue(ShortTickCountProperty); }
set { SetValue(ShortTickCountProperty, value); }
}
#endregion
#region TickDurtion 刻度改变时的动画显示时长
public static readonly DependencyProperty TickDurtionProperty = DependencyProperty.Register("TickDurtion"
, typeof(Duration)
, typeof(Dashboard),
new PropertyMetadata(new Duration(TimeSpan.FromMilliseconds(400))));
/// <summary>
/// 刻度改变时的动画显示时长
/// </summary>
public Duration TickDurtion
{
get { return (Duration)GetValue(TickDurtionProperty); }
set { SetValue(TickDurtionProperty, value); }
}
#endregion
#region ShortTicksBrush 短刻度颜色
public static readonly DependencyProperty ShortTicksBrushProperty = DependencyProperty.Register("ShortTicksBrush"
, typeof(Brush)
, typeof(Dashboard));
/// <summary>
/// 短刻度颜色
/// </summary>
public Brush ShortTicksBrush
{
get { return (Brush)GetValue(ShortTicksBrushProperty); }
set { SetValue(ShortTicksBrushProperty, value); }
}
#endregion
#region LongTicksBrush 长刻度颜色
public static readonly DependencyProperty LongTicksBrushProperty = DependencyProperty.Register("LongTicksBrush"
, typeof(Brush)
, typeof(Dashboard));
/// <summary>
/// 长刻度颜色
/// </summary>
public Brush LongTicksBrush
{
get { return (Brush)GetValue(LongTicksBrushProperty); }
set { SetValue(LongTicksBrushProperty, value); }
}
#endregion
#region Content
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(Dashboard));
#endregion
#region ContentTemplate
public DataTemplate ContentTemplate
{
get { return (DataTemplate)GetValue(ContentTemplateProperty); }
set { SetValue(ContentTemplateProperty, value); }
}
public static readonly DependencyProperty ContentTemplateProperty =
DependencyProperty.Register("ContentTemplate", typeof(DataTemplate), typeof(Dashboard));
#endregion
#endregion
#region Private依赖属性
#region Angle 刻度盘当前值所对应的角度
/// <summary>
/// 刻度盘当前值所对应的角度依赖属性
/// </summary>
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register(
"Angle",
typeof(double),
typeof(Dashboard),
new PropertyMetadata(0d));
/// <summary>
/// 刻度盘当前值所对应的角度
/// </summary>
public double Angle
{
get { return (double)GetValue(AngleProperty); }
private set { SetValue(AngleProperty, value); }
}
#endregion
#region ShortTicks 短刻度线集合
/// <summary>
/// 短刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty ShortTicksProperty =
DependencyProperty.Register(
"ShortTicks",
typeof(IList<object>),
typeof(Dashboard),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置短刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>短刻度线.</value>
public IList<object> ShortTicks
{
get { return (IList<object>)GetValue(ShortTicksProperty); }
private set { SetValue(ShortTicksProperty, value); }
}
#endregion
#region LongTicks 长刻度线集合
/// <summary>
/// 长刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty LongTicksProperty =
DependencyProperty.Register(
"LongTicks",
typeof(IList<object>),
typeof(Dashboard),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置长刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>长刻度线.</value>
public IList<object> LongTicks
{
get { return (IList<object>)GetValue(LongTicksProperty); }
private set { SetValue(LongTicksProperty, value); }
}
#endregion
#region LongTicks 长刻度线上显示的数字
/// <summary>
/// 长刻度线依赖属性,用于Binding
/// </summary>
public static readonly DependencyProperty NumberListProperty =
DependencyProperty.Register(
"NumberList",
typeof(IList<object>),
typeof(Dashboard),
new PropertyMetadata(null));
/// <summary>
/// 获取或设置长刻度线,用于绑定PathListBox的ItemsSource
/// </summary>
/// <value>长刻度线.</value>
public IList<object> NumberList
{
get { return (IList<object>)GetValue(NumberListProperty); }
private set { SetValue(NumberListProperty, value); }
}
#endregion
#endregion
#region 重载
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_IncreaseCircle = GetTemplateChild("PART_IncreaseCircle") as Arc;
this.SetTicks();
this.SetAngle();
this.TransformAngle();
}
#endregion
#region Private方法
/// <summary>
/// 设置刻度线
/// </summary>
private void SetTicks()
{
List<object> numbers = new List<object>();
List<object> shortticks = new List<object>();
List<object> longticks = new List<object>();
for (int i = 0; i < this.LongTickCount; i++)
{
numbers.Add(Math.Round(this.Minimum + (this.Maximum - this.Minimum) / (this.LongTickCount - 1) * i));
longticks.Add(new object());
}
for (int i = 0; i < (this.LongTickCount - 1) * (this.ShortTickCount + 1) + 1; i++)
{
shortticks.Add(new object());
}
this.ShortTicks = shortticks;
this.LongTicks = longticks;
this.NumberList = numbers;
}
/// <summary>
/// 根据当前值设置圆弧的EndAngle
/// </summary>
private void SetAngle()
{
if (this.Value < this.Minimum)
{
this.Angle = this.StartAngle;
return;
}
if (this.Value > this.Maximum)
{
this.Angle = this.EndAngle;
return;
}
var diff = this.Maximum - this.Minimum;
var valueDiff = this.Value - this.Minimum;
this.Angle = this.StartAngle + (this.EndAngle - this.StartAngle) / diff * valueDiff;
}
/// <summary>
/// 角度值变化动画
/// </summary>
private void TransformAngle()
{
if (this.PART_IncreaseCircle != null)
{
DoubleAnimation doubleAnimation = new DoubleAnimation(this.OldAngle, this.Angle, this.TickDurtion);
this.PART_IncreaseCircle.BeginAnimation(Arc.EndAngleProperty, doubleAnimation);
}
}
#endregion
}
}
设置刻度盘的style:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:ec="http://schemas.microsoft.com/expression/2010/controls"
xmlns:local ="clr-namespace:Dashboard_Demo">
<!-- 流量盘 -->
<ControlTemplate x:Key="Flow" TargetType="{x:Type local:Dashboard}">
<Grid>
<!-- 刻度盘完整圆弧 -->
<ed:Arc x:Name="DoubleCircle" Margin="50" ArcThickness="3" ArcThicknessUnit="Pixel"
EndAngle="{TemplateBinding EndAngle}"
SnapsToDevicePixels="True"
StartAngle="{TemplateBinding StartAngle}"
Stretch="None" Stroke="#002266" Opacity="0.16" StrokeThickness="3" UseLayoutRounding="True" />
<!-- 刻度盘当前值对应的圆弧 -->
<ed:Arc x:Name="PART_IncreaseCircle" Margin="50" ArcThickness="3" ArcThicknessUnit="Pixel"
RenderTransformOrigin="0.5,0.5"
StartAngle="{TemplateBinding StartAngle}"
Stretch="None" StrokeThickness="3" />
<!-- 短刻度 -->
<ec:PathListBox x:Name="ShoartTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding ShortTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="1" Height="8"
Background="{Binding ShortTicksBrush,
RelativeSource={RelativeSource AncestorType={x:Type local:Dashboard}}}"
SnapsToDevicePixels="True" UseLayoutRounding="False" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=ShortTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<!-- 长刻度 -->
<ec:PathListBox x:Name="LongTick" IsHitTestVisible="False"
ItemsSource="{TemplateBinding LongTicks}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<Border Width="1" Height="13"
Background="{Binding LongTicksBrush,
RelativeSource={RelativeSource AncestorType={x:Type local:Dashboard}}}"
SnapsToDevicePixels="True" UseLayoutRounding="False" />
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=LongTickPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<!-- 刻度上显示的数字 -->
<ec:PathListBox x:Name="Number" IsHitTestVisible="False"
ItemsSource="{TemplateBinding NumberList}">
<ec:PathListBox.ItemTemplate>
<DataTemplate>
<TextBlock RenderTransformOrigin="0.5,0.5" Text="{Binding}">
</TextBlock>
</DataTemplate>
</ec:PathListBox.ItemTemplate>
<ec:PathListBox.LayoutPaths>
<ec:LayoutPath Distribution="Even" Orientation="OrientToPath"
SourceElement="{Binding ElementName=NumberPath}" />
</ec:PathListBox.LayoutPaths>
</ec:PathListBox>
<!--#region 路径-->
<ed:Arc x:Name="ShortTickPath" Margin="30" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="{TemplateBinding EndAngle}"
StartAngle="{TemplateBinding StartAngle}"
Stretch="None" />
<ed:Arc x:Name="LongTickPath" Margin="25" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="{TemplateBinding EndAngle}"
StartAngle="{TemplateBinding StartAngle}"
Stretch="None" />
<ed:Arc x:Name="NumberPath" Margin="10" ArcThickness="0" ArcThicknessUnit="Pixel"
EndAngle="{TemplateBinding EndAngle}"
StartAngle="{TemplateBinding StartAngle}"
Stretch="None" />
<!--#endregion-->
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="DefaultLabelPanel" x:Shared="False">
<Border Width="70" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom"
BorderBrush="#00A0FB" BorderThickness="1" CornerRadius="3" Padding="0,2"
SnapsToDevicePixels="True" UseLayoutRounding="True">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Agency FB" Foreground="White"
Text="{Binding Path=Value,
StringFormat={}{0:N1}KW,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:Dashboard}}}" />
</Border>
</DataTemplate>
<Style TargetType="{x:Type local:Dashboard}">
<Setter Property="StartAngle" Value="-140" />
<Setter Property="EndAngle" Value="140" />
<Setter Property="Foreground" Value="#929093" />
<Setter Property="BorderBrush" Value="#746E7A" />
<Setter Property="ShortTicksBrush" Value="#746E7A" />
<Setter Property="LongTicksBrush" Value="#746E7A" />
<Setter Property="Template" Value="{StaticResource Flow}" />
<Setter Property="ContentTemplate" Value="{StaticResource DefaultLabelPanel}" />
</Style>
<LinearGradientBrush x:Key="LargeArcCenterBackground" StartPoint="0.0,0" EndPoint="0,1">
<GradientStop Color="#d84a36" Offset="0"/>
<GradientStop Color="#ec7c6c" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="LargeOutArcBackground" StartPoint="0.0,0" EndPoint="0,1">
<GradientStop Color="#e2aaa3" Offset="0"/>
<GradientStop Color="#e4b4ac" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="LargeBorderArcBackground" StartPoint="0.0,0" EndPoint="0,1">
<GradientStop Color="#e3d5d3" Offset="0"/>
<GradientStop Color="#e4d8d6" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="SmallArcCenterBackground" StartPoint="0.0,0" EndPoint="0,1">
<GradientStop Color="#389cfa" Offset="0"/>
<GradientStop Color="#73b8fd" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="SmallOutArcBackground" StartPoint="0.0,0" EndPoint="0,1" Opacity="0.4">
<GradientStop Color="#389AfC" Offset="0"/>
<GradientStop Color="#78AEFC" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="SmallBorderArcBackground" StartPoint="0.0,0" EndPoint="0,1" Opacity="0.1">
<GradientStop Color="#389AfC" Offset="0"/>
<GradientStop Color="#78AEFC" Offset="1"/>
</LinearGradientBrush>
</ResourceDictionary>
- 库容总数背景渐变实现:
-
<DataTemplate x:Key="Small"> <Grid Margin="0,110,0,0" HorizontalAlignment="Center"> <Ellipse x:Name="ellipse1" Width="166" Height="166" Fill="{StaticResource SmallBorderArcBackground}" Margin="0 -43" VerticalAlignment="Top"/> <Ellipse x:Name="ellipse2" Width="147" Height="147" Fill="{StaticResource SmallOutArcBackground}" Margin="0 -33" VerticalAlignment="Top"/> <Ellipse x:Name="ellipse3" Width="133" Height="133" Fill="{StaticResource SmallArcCenterBackground}" Margin="0 -25" VerticalAlignment="Top"/> <TextBlock Margin="0,30" HorizontalAlignment="Center" FontSize="24" Foreground="White" FontWeight="Bold" Text="{Binding Path=Value, StringFormat={}{0:N0}, ElementName=dashboard1}" /> </Grid> </DataTemplate> <DataTemplate x:Key="Large"> <Grid Margin="0,110,0,0" HorizontalAlignment="Center"> <Ellipse x:Name="ellipse1" Width="166" Height="166" Fill="{StaticResource LargeBorderArcBackground}" Margin="0 -43" VerticalAlignment="Top"/> <Ellipse x:Name="ellipse2" Width="147" Height="147" Fill="{StaticResource LargeOutArcBackground}" Margin="0 -33" VerticalAlignment="Top"/> <Ellipse x:Name="ellipse3" Width="133" Height="133" Fill="{StaticResource LargeArcCenterBackground}" Margin="0 -25" VerticalAlignment="Top"/> <TextBlock Margin="0,30" HorizontalAlignment="Center" FontSize="24" Foreground="White" FontWeight="Bold" Text="{Binding Path=Value, StringFormat={}{0:N0}, ElementName=dashboard1}" /> </Grid> </DataTemplate>
实现效果(低于80W):
-
高于80W时显示:
-
csdn下载地址:https://download.csdn.net/download/chulijun3107/88058570
-
github:GitHub - chulijun3107/Dashboard_Demo: WPF userControl