tags:

views:

466

answers:

1

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  A: 

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());
        }
    }
}
ghostskunks