WPF 自定义控件完成库容表盘显示效果

先看一下显示效果:

       需要注意的地方有以下几点:

  1. 表盘的刻度分部,长刻度和短刻度显示。
  2. 在数值80W时,需要更改刻度盘的颜色渐变。
  3. 在数值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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚楚3107

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值