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());
}
}
}
Leave out the Storyboard:
(small fix for syntax)
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:
I have a Solution using XAML / C# Combo. In your XAML file define:
This will give you the ability to do the following in the C# code:
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.
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.