【问题标题】:WPF create dashed ellipse of individual blocksWPF 创建单个块的虚线椭圆
【发布时间】:2017-01-19 16:22:05
【问题描述】:

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

类似这样的:

我希望能够单独控制每个破折号,例如更改其颜色或将其绑定到我的视图模型中的操作。

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

【问题讨论】:

  • 您需要一个带有模板的 ItemsControl,该模板创建一个“破折号”并使用 RenderTransform 进行旋转,旋转量绑定到模板化对象中的值。将您的 ItemsControl 绑定到对象集合,每个对象都具有旋转值属性、颜色或画笔属性和命令。在模板中,您可以进行绑定以在单击或其他任何情况下调用命令。
  • 没想到......它会创建一个完美的椭圆,其中间隙大小也一致吗?我还想稍后在这个椭圆内添加一些东西,比如文本和其他元素。
  • 如果你想要一个完美的圆圈,这很容易。如果你想要任何椭圆,你将不得不更加努力地工作。在中间添加额外的内容是微不足道的。
  • 完美的圆很好,不需要古怪的椭圆。我会同时尝试这种方法,看看它是否满足我的要求。谢谢。
  • @EdPlunkett Awww 该死的 Ed,我什至没有阅读 OP 要求的那部分内容。为此,我将采用以下几种方法之一...就我个人而言,我会使用椭圆工具在 Illustrator 中创建资产,设置笔划破折号,然后将其转换为单独的路径并导出到 xaml,以便您可以将事件附加到每个单独路径。如果以后有时间,我会提供一个例子。

标签: c# wpf xaml geometry ellipse


【解决方案1】:

我现在回到这个问题,并设法以一种非常灵活和通用的方式解决它。此后需求有所变化,无需绑定,但可以轻松添加。

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

这是我想出的 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" />

结果:

破折号的数量,动画的长度和速度等......都是可配置的。不过,依赖属性的命名可能会更好......

享受:-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    相关资源
    最近更新 更多