C# WPF创建单个块的虚线椭圆

C# WPF创建单个块的虚线椭圆,c#,wpf,xaml,geometry,ellipse,C#,Wpf,Xaml,Geometry,Ellipse,有没有简单的方法来创建由单个水平虚线组成的虚线椭圆,其中虚线大小一致,并且可以指定其数量 大概是这样的: 我希望能够单独控制每个破折号,比如更改其颜色或将其绑定到viewmodel中的动作 我能想到的实现这一点的唯一方法是创建一个自定义控件,该控件包含每个破折号的路径元素,共同组成一个椭圆形状,必须根据破折号的数量和椭圆的大小计算路径数据。我现在回到这个问题上来,并设法以非常灵活和通用的方式解决它。从那时起,需求发生了一些变化,不需要绑定,但可以轻松添加 注意,这是一个圆,这是我想要的。问题应

有没有简单的方法来创建由单个水平虚线组成的虚线椭圆,其中虚线大小一致,并且可以指定其数量

大概是这样的:

我希望能够单独控制每个破折号,比如更改其颜色或将其绑定到viewmodel中的动作


我能想到的实现这一点的唯一方法是创建一个自定义控件,该控件包含每个破折号的路径元素,共同组成一个椭圆形状,必须根据破折号的数量和椭圆的大小计算路径数据。

我现在回到这个问题上来,并设法以非常灵活和通用的方式解决它。从那时起,需求发生了一些变化,不需要绑定,但可以轻松添加

注意,这是一个圆,这是我想要的。问题应该是圆而不是椭圆,即使圆是椭圆,但我离题了

下面是我提出的用户控件:

StatusRing.xaml.cs

public partial class StatusRing
{
    #region Dependency Property registrations

    public static readonly DependencyProperty DashesProperty = DependencyProperty.Register("Dashes",
        typeof(int), typeof(StatusRing), new PropertyMetadata(32, DashesChanged));

    public static readonly DependencyProperty DiameterProperty = DependencyProperty.Register("Diameter",
        typeof(double), typeof(StatusRing), new PropertyMetadata(150.00, DiameterChanged));

    public static readonly DependencyProperty DashHeightProperty = DependencyProperty.Register("DashHeight",
        typeof(double), typeof(StatusRing), new PropertyMetadata(20.00, DashHeightChanged));

    public static readonly DependencyProperty DashWidthProperty = DependencyProperty.Register("DashWidth",
        typeof(double), typeof(StatusRing), new PropertyMetadata(5.00, DashWidthChanged));

    public static readonly DependencyProperty DashFillProperty = DependencyProperty.Register("DashFill",
        typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.DimGray, DashFillChanged));

    public static readonly DependencyProperty DashAccentFillProperty = DependencyProperty.Register("DashAccentFill",
        typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.White, DashAnimationFillChanged));

    public static readonly DependencyProperty TailSizeProperty = DependencyProperty.Register("TailSize",
        typeof(int), typeof(StatusRing), new PropertyMetadata(10, TailSizeChanged));

    public static readonly DependencyProperty AnimationSpeedProperty = DependencyProperty.Register("AnimationSpeed",
        typeof(double), typeof(StatusRing), new PropertyMetadata(50.00, AnimationSpeedChanged));

    public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register("IsPlaying",
        typeof(bool), typeof(StatusRing), new PropertyMetadata(false, IsPlayingChanged));

    #endregion Dependency Property registrations

    private readonly Storyboard glowAnimationStoryBoard = new Storyboard();

    public StatusRing()
    {
        Loaded += OnLoaded;
        InitializeComponent();
    }

    #region Dependency Properties

    public int Dashes
    {
        get => (int)GetValue(DashesProperty);
        set => SetValue(DashesProperty, value);
    }

    public double Diameter
    {
        get => (double)GetValue(DiameterProperty);
        set => SetValue(DiameterProperty, value);
    }

    public double Radius => Diameter / 2;

    public double DashHeight
    {
        get => (double)GetValue(DashHeightProperty);
        set => SetValue(DashHeightProperty, value);
    }

    public double DashWidth
    {
        get => (double)GetValue(DashWidthProperty);
        set => SetValue(DashWidthProperty, value);
    }

    public Brush DashFill
    {
        get => (SolidColorBrush)GetValue(DashFillProperty);
        set => SetValue(DashFillProperty, value);
    }

    public Brush DashAccentFill
    {
        get => (SolidColorBrush)GetValue(DashAccentFillProperty);
        set => SetValue(DashAccentFillProperty, value);
    }

    public int TailSize
    {
        get => (int)GetValue(TailSizeProperty);
        set => SetValue(TailSizeProperty, value);
    }

    public double AnimationSpeed
    {
        get => (double)GetValue(AnimationSpeedProperty);
        set => SetValue(AnimationSpeedProperty, value);
    }

    public bool IsPlaying
    {
        get => (bool)GetValue(IsPlayingProperty);
        set => SetValue(IsPlayingProperty, value);
    }

    #endregion Dependency Properties

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var thisControl = sender as StatusRing;
        Recreate(thisControl);
    }

    #region Dependency Property callbacks

    private static void DashesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void DiameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void DashHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void DashWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void DashFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void DashAnimationFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void TailSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        Recreate(thisControl);
    }

    private static void AnimationSpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        if (thisControl.IsLoaded)
        {
            thisControl.glowAnimationStoryBoard.Stop();
            thisControl.glowAnimationStoryBoard.Children.Clear();

            ApplyAnimations(thisControl);

            thisControl.glowAnimationStoryBoard.Begin();
        }
    }

    private static void IsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var thisControl = d as StatusRing;
        if (thisControl.IsLoaded)
        {
            var isPlaying = (bool)e.NewValue;
            if (isPlaying)
            {
                thisControl.glowAnimationStoryBoard.Begin();
            }
            else
            {
                thisControl.glowAnimationStoryBoard.Stop();
            }
        }
    }

    #endregion Dependency Property callbacks

    private static void Recreate(StatusRing thisControl)
    {
        if (thisControl.IsLoaded)
        {
            thisControl.glowAnimationStoryBoard.Stop();
            thisControl.glowAnimationStoryBoard.Children.Clear();
            thisControl.RootCanvas.Children.Clear();

            Validate(thisControl);
            BuildRing(thisControl);

            ApplyAnimations(thisControl);

            if (thisControl.IsPlaying)
            {
                thisControl.glowAnimationStoryBoard.Begin();
            }
            else
            {
                thisControl.glowAnimationStoryBoard.Stop();
            }
        }
    }

    private static void Validate(StatusRing thisControl)
    {
        if (thisControl.TailSize > thisControl.Dashes)
        {
            throw new Exception("TailSize cannot be larger than amount of dashes");
        }
    }

    private static void BuildRing(StatusRing thisControl)
    {
        var angleStep = (double)360 / thisControl.Dashes;

        for (double i = 0; i < 360; i = i + angleStep)
        {
            var rect = new Rectangle
            {
                Fill = thisControl.DashFill,
                Height = thisControl.DashHeight,
                Width = thisControl.DashWidth
            };

            //Rotate dash to follow circles circumference 
            var centerY = thisControl.Radius;
            var centerX = thisControl.DashWidth / 2;
            var rotateTransform = new RotateTransform(i, centerX, centerY);
            rect.RenderTransform = rotateTransform;

            var offset = thisControl.Radius - thisControl.DashWidth / 2;
            rect.SetValue(Canvas.LeftProperty, offset);

            thisControl.RootCanvas.Children.Add(rect);
        }

        thisControl.RootCanvas.Width = thisControl.Diameter;
        thisControl.RootCanvas.Height = thisControl.Diameter;
    }

    private static void ApplyAnimations(StatusRing thisControl)
    {
        var baseColor = ((SolidColorBrush)thisControl.DashFill).Color;
        var animatedColor = ((SolidColorBrush)thisControl.DashAccentFill).Color;

        var dashes = thisControl.RootCanvas.Children.OfType<Rectangle>().ToList();

        double animationPeriod = thisControl.AnimationSpeed;
        double glowDuration = animationPeriod * thisControl.TailSize;

        for (int i = 0; i < dashes.Count; i++)
        {
            var beginTime = TimeSpan.FromMilliseconds(animationPeriod * i);

            var colorAnimation = new ColorAnimationUsingKeyFrames
            {
                BeginTime = beginTime,
                RepeatBehavior = RepeatBehavior.Forever
            };

            var toFillColor = new LinearColorKeyFrame(animatedColor, TimeSpan.Zero);
            colorAnimation.KeyFrames.Add(toFillColor);

            var dimToBase = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(glowDuration));
            colorAnimation.KeyFrames.Add(dimToBase);

            var restingTime = animationPeriod * dashes.Count;
            var delay = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(restingTime));
            colorAnimation.KeyFrames.Add(delay);

            Storyboard.SetTarget(colorAnimation, dashes[i]);
            Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("(Fill).(SolidColorBrush.Color)"));

            thisControl.glowAnimationStoryBoard.Children.Add(colorAnimation);
        }
    }
}
公共部分类StatusRing
{
#地区从属财产登记
public static readonly dependencProperty dashProperty=dependencProperty.Register(“破折号”),
typeof(int)、typeof(StatusRing)、新的PropertyMetadata(32,DashesChanged));
公共静态只读DependencyProperty DiameterProperty=DependencyProperty.Register(“直径”,
typeof(双精度)、typeof(StatusRing)、新的PropertyMetadata(150.00,直径更改));
公共静态只读DependencyProperty DashHeightProperty=DependencyProperty.Register(“DashHeight”,
typeof(双精度)、typeof(StatusRing)、新的PropertyMetadata(20.00,DashHeightChanged));
公共静态只读DependencyProperty DashWidthProperty=DependencyProperty.Register(“DashWidth”,
typeof(双精度)、typeof(StatusRing)、新PropertyMetadata(5.00,DashWidthChanged));
公共静态只读DependencyProperty DashFillProperty=DependencyProperty.Register(“DashFill”,
typeof(SolidColorBrush)、typeof(StatusRing)、新属性元数据(Brush.DimGray、DashFillChanged));
公共静态只读DependencyProperty DashAccentFillProperty=DependencyProperty.Register(“DashAccentFill”,
typeof(SolidColorBrush)、typeof(StatusRing)、新属性元数据(Brush.White、DashAnimationFillChanged));
公共静态只读DependencyProperty TailSizeProperty=DependencyProperty.Register(“TailSize”,
typeof(int)、typeof(StatusRing)、新属性ymatadata(10,TailSizeChanged));
公共静态只读DependencyProperty AnimationSpeedProperty=DependencyProperty.Register(“AnimationSpeed”,
typeof(double)、typeof(StatusRing)、新属性ymetadata(50.00,AnimationSpeedChanged));
public static readonly dependencProperty IsPlayingProperty=dependencProperty.Register(“IsPlaying”,
typeof(bool)、typeof(StatusRing)、新属性元数据(false、IsPlayingChanged);
#endregion依赖项属性注册
私有只读故事板glowAnimationStoryBoard=新故事板();
公共状态环()
{
已加载+=已加载;
初始化组件();
}
#区域依赖属性
公共整型破折号
{
get=>(int)GetValue(DashsProperty);
set=>SetValue(dashsProperty,value);
}
公共双直径
{
get=>(双精度)GetValue(DiameterProperty);
set=>SetValue(直径属性,值);
}
公共双半径=>直径/2;
公共双高
{
get=>(双精度)GetValue(DashHeightProperty);
set=>SetValue(DashHeightProperty,value);
}
公共双短划线宽度
{
get=>(双精度)GetValue(DashWidthProperty);
set=>SetValue(DashWidthProperty,value);
}
公共灌木丛
{
get=>(SolidColorBrush)GetValue(DashFillProperty);
set=>SetValue(DashFillProperty,value);
}
公共灌木丛
{
get=>(SolidColorBrush)GetValue(DashAccentFillProperty);
set=>SetValue(属性,值);
}
公共整数尾码
{
get=>(int)GetValue(TailSizeProperty);
set=>SetValue(TailSizeProperty,value);
}
公共双动画速度
{
get=>(双精度)GetValue(AnimationSpeedProperty);
set=>SetValue(AnimationSpeedProperty,value);
}
公共广播
{
get=>(bool)GetValue(iPlayingProperty);
set=>SetValue(IsPlayingProperty,value);
}
#endregion依赖属性
已加载专用void(对象发送方,RoutedEventArgs e)
{
var thisControl=发送方为StatusRing;
重新创建(此控件);
}
#区域依赖属性回调
私有静态无效DashChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
专用静态空隙直径已更改(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
私有静态无效DashHeightChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
私有静态无效DashWidthChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
私有静态无效DashFillChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
私有静态虚空DashAnimationFillChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var thisControl=d作为StatusRing;
重新创建(此控件);
}
私有静态无效TailSizeChanged(DependencyObject d,DependencyP
<UserControl x:Class="WpfPlayground.StatusRing"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Canvas x:Name="RootCanvas" />
<local:StatusRing Diameter="250" 
                  Dashes="32"
                  TailSize="16"
                  IsPlaying="True" />