I've been working with animation in WPF recently, and overall I'm really impressed. Like most of WPF, I'm finding it takes a mind shift in the way you think about how you would normally go about implementing certain features. In a lot of ways, you need to give up control of elements that you traditionally would have had pretty tight control over, such as the layout of UI elements. You just hand over some excruciatingly verbose XAML, and off it goes and does its thing.
But anyway, mindless rambling aside, today I was trying to animate the position of a GeometyModel3D object using a key framed animation. The GeometryModel3D class exposes a Transform property that allows you to control the position and rotation of a model. So I added a TranslateTransform3D element to my geometry's XAML, named it so it could be manipulated directly from my code behind, and went to add a Point3DAnimationUsingKeyFrames assuming that the TranslateTransform3D class would expose either a Point3D property, or at the very least a Vector3D. But no, said WPF, no dice. No property for you! The TranslateTranform3D class exposes three properties by which you can affect position, OffsetX, OffsetY, and OffsetZ, which means that if you want to do a key-framed animation of the position of a 3D object, you have to create three separate DoubleAnimationUsingKeyFrames timelines, targeting the x, y, and z offset properties of the TranslateTransform3D respectively. Within each of those timelines, you then have to create the key frames for the x, y, and z positions individually. "My arse", I said to WPF.
"Ok then", said WPF, "how about this. I can animate any dependency property that belongs to a class that inherits DependencyObject and implements IAnimatable, and is of the same type as one of my built in animation classes, those being Double, Boolean, Byte, Color, you know, pretty much every type you'd ever want to animate." That sounded good. If I could wrap the three offset positions from the TranslateTransform3D class behind a single dependency property, then that property could be animated and the three offset positions could be set from inside the property setter.
So I created a dependency property with the following code:
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register(?Position?, typeof(Point3D), typeof(TurntableItem),
new PropertyMetadata());
and the CLR property:
public Point3D Position
{
get { return (Point3D)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
with the intention of setting the offsets from the setter of Position. Unfortunately, the setter isn't called as the animation updates because before the first frame of an animation is run, a copy of the target property is created and used over the duration. After much contemplating of my navel, a colleague sent me a link to this blog post -
WPF Animation. This guy had the same problem, and a slightly different solution, but it turns out that you can use a PropertyChangedCallback delegate as a parameter to the PropertyMetaData argument in the dependency property Register method, which does get called every animation update. So what I now have is the following:
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register(?Position?, typeof(Point3D), typeof(TurntableItem),
new PropertyMetadata(new PropertyChangedCallback(PositionChangedCallback)));private static void PositionChangedCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
TurntableItem item = (TurntableItem)d;
item.TurntableItemTransform.OffsetX = ((Point3D)e.NewValue).X;
item.TurntableItemTransform.OffsetY = ((Point3D)e.NewValue).Y;
item.TurntableItemTransform.OffsetZ = ((Point3D)e.NewValue).Z;
}
Works a treat!