WPF: Animating TranslateTransform from code

2020-05-23 08:39发布

问题:

I have a WPF canvas on which I'm dynamically creating objects from code. These objects are being transformed by setting the RenderTransform property, and an animation needs to be applied one of those transforms. Currently, I can't get properties of any transform to animate (although no exception gets raised and the animation appears to run - the completed event gets raised).

In addition, if the animation system is stressed, sometimes the Storyboard.Completed event is never raised.

All the examples I've come accross animate the transforms from XAML. MSDN documentation suggests that the x:Name property of a transform must be set for it to be animatable, but I haven't found a working way to set it from code.

Any ideas?

Here's the full code listing that reproduces the problem:

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace AnimationCompletedTest {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        Canvas panel;
        public MainWindow() {
            InitializeComponent();
            MouseDown += DoDynamicAnimation;

            Content = panel = new Canvas();
        }

        void DoDynamicAnimation(object sender, MouseButtonEventArgs args) {

            for (int i = 0; i < 12; ++i) {
                var e = new Ellipse {
                    Width = 16,
                    Height = 16,
                    Fill = SystemColors.HighlightBrush
                };
                Canvas.SetLeft(e, Mouse.GetPosition(this).X);
                Canvas.SetTop(e, Mouse.GetPosition(this).Y);

                var tg = new TransformGroup();
                var translation = new TranslateTransform(30, 0);
                tg.Children.Add(translation);
                tg.Children.Add(new RotateTransform(i * 30));
                e.RenderTransform = tg;

                panel.Children.Add(e);

                var s = new Storyboard();
                Storyboard.SetTarget(s, translation);
                Storyboard.SetTargetProperty(s, new PropertyPath(TranslateTransform.XProperty));

                s.Children.Add(
                    new DoubleAnimation(3, 100, new Duration(new TimeSpan(0, 0, 0, 1, 0))) {
                        EasingFunction = new PowerEase {EasingMode = EasingMode.EaseOut}
                    });

                s.Completed += 
                    (sndr, evtArgs) => {
                        Debug.WriteLine("Animation {0} completed {1}", s.GetHashCode(), Stopwatch.GetTimestamp());
                        panel.Children.Remove(e);
                    };

                Debug.WriteLine("Animation {0} started {1}", s.GetHashCode(), Stopwatch.GetTimestamp());

                s.Begin();
            }
        }

        [STAThread]
        public static void Main() {
            var app = new Application();
            app.Run(new MainWindow());
        }
    }
}

回答1:

Leave out the Storyboard:

var T = new TranslateTransform(40, 0);
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0));
DoubleAnimation anim = new DoubleAnimation(30, duration);
T.BeginAnimation(TranslateTransform.YProperty, anim);

(small fix for syntax)



回答2:

Seems that after a bit of Googling I solved the problem myself. Many thanks to MSDN documentation and a post in MSDN forums by Antares19.

In summary:

  • For a Freezable object (like TranslateTransform) to be targetable by an Storyboard, it must have a registered name. This can be done by calling FrameworkElement.RegisterName(..).

  • I added the Storyboard object to the ResourceDictionary of the same Framework element to which I registered the TranslateTransform. This can be done by calling ResourceDictionary.Add(..)

Here's the updated code, which now animates nicely, and registers/deregisters the added resources:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace AnimationCompletedTest {

    public partial class MainWindow : Window {

        Canvas panel;
        public MainWindow() {
            InitializeComponent();
            MouseDown += DoDynamicAnimation;

            Content = panel = new Canvas();
        }

        void DoDynamicAnimation(object sender, MouseButtonEventArgs args) {
            for (int i = 0; i < 12; ++i) {
                var e = new Ellipse { Width = 16, Height = 16, Fill = SystemColors.HighlightBrush };
                Canvas.SetLeft(e, Mouse.GetPosition(this).X);
                Canvas.SetTop(e, Mouse.GetPosition(this).Y);

                var tg = new TransformGroup();
                var translation = new TranslateTransform(30, 0);
                var translationName = "myTranslation" + translation.GetHashCode();
                RegisterName(translationName, translation);
                tg.Children.Add(translation);
                tg.Children.Add(new RotateTransform(i * 30));
                e.RenderTransform = tg;

                panel.Children.Add(e);

                var anim = new DoubleAnimation(3, 100, new Duration(new TimeSpan(0, 0, 0, 1, 0))) {
                    EasingFunction = new PowerEase { EasingMode = EasingMode.EaseOut }
                };

                var s = new Storyboard();
                Storyboard.SetTargetName(s, translationName);
                Storyboard.SetTargetProperty(s, new PropertyPath(TranslateTransform.YProperty));
                var storyboardName = "s" + s.GetHashCode();
                Resources.Add(storyboardName, s);

                s.Children.Add(anim);

                s.Completed +=
                    (sndr, evtArgs) => {
                        panel.Children.Remove(e);
                        Resources.Remove(storyboardName);
                        UnregisterName(translationName);
                    };
                s.Begin();
            }
        }

        [STAThread]
        public static void Main() {
            var app = new Application();
            app.Run(new MainWindow());
        }
    }
}


回答3:

I have a Solution using XAML / C# Combo. In your XAML file define:

<UserControl.RenderTransform>
    <TranslateTransform x:Name="panelTrans" Y="0"></TranslateTransform>
</UserControl.RenderTransform>

This will give you the ability to do the following in the C# code:

        Storyboard.SetTargetName(mFlyInDA, "panelTrans");
        Storyboard.SetTargetProperty(mFlyInDA, new PropertyPath("Y"));

The rest is business as usual. Create a DoubleAnimation, set its properties, add it as a child to your Storyboard, call the Begin function on the story board.



回答4:

No need to register the transform or add the storyboard to the resource collection if the target is the FrameworkElement you want to animate. You can use Blend to generate the PropertyPath syntax.

frameworkElement.RenderTransform = new TransformGroup
        {
            Children =
            {
                new ScaleTransform(),
                new SkewTransform(),
                new RotateTransform(),
                translate
            }
        };
translate.X = 100.0;
var animation = new DoubleAnimation(0.0, TimeSpan.FromSeconds(1));
var sb = new Storyboard();            
sb.Children.Add(animation);
Storyboard.SetTarget(animation, frameworkElement);
Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"));
sb.Begin();


标签: c# wpf animation