WPF创建由单独块组成的虚线椭圆

4

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

像这样的:

enter image description here

我希望能够单独控制每个破折号,比如更改其颜色或将其绑定到视图模型中的操作。
我能想到的唯一实现方法是创建一个自定义控件,其中包含每个破折号的路径元素,共同组成一个椭圆形状,必须根据破折号数量和椭圆形状的大小计算路径数据。

2
您想要一个ItemsControl,它具有一个模板,该模板可以创建一个“dash”并使用RenderTransform进行旋转,旋转的角度与模板对象中的某个值绑定。将您的ItemsControl绑定到一个包含多个对象的集合,每个对象都具有旋转值属性、颜色或画笔属性以及一个命令。在模板中,您可以使用绑定来在点击或其他事件时调用命令。 - 15ee8f99-57ff-4f92-890c-b56153
没想到这个问题...它会创建一个完美的椭圆,其中间隙大小也是一致的吗?我还想在这个椭圆内添加其他元素,比如文本和其他元素。 - Shahin Dohan
完美的圆形就很好,不需要奇怪的椭圆形。与此同时,我会尝试这种方法,看看它是否满足我的要求。谢谢。 - Shahin Dohan
你只需要设置 StrokeDashOffsetStrokeDashArray - 参考我的蚂蚁行进 示例,并调整这些值以获得所需的外观,使用 Ellipse 替代 Rectangle 即可 :) - Chris W.
1
@EdPlunkett 哎呀,天哪,Ed,我甚至没有读到OP要求的那一部分。为了做到这一点,我会采取以下几种方式之一...个人而言,我会使用椭圆工具在Illustrator中创建资产,设置笔画虚线,然后将其转换为单独的路径并导出到xaml,这样您就可以为每个路径单独附加事件。如果我晚些时候有时间,我会提供一个示例。 - Chris W.
显示剩余2条评论
1个回答

2
我现在回到这个问题,并以一种非常灵活和通用的方式解决了它。自那时以来,要求有些变化,不需要绑定,但可以很容易地添加。
请注意,这是一个圆,这正是我想要的。问题应该真正说是圆而不是椭圆,尽管圆是椭圆,但我偏离了主题...
这是我想出的UserControl:

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.xaml:

<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" />

结果:

StatusRing in action

“破折号数量、动画长度和速度等都可以进行配置。但依赖属性的命名可能需要改进...”
“祝您愉快 :-)”

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接