Custom Panels in Silverlight/WPF Part 3: Animation
This is Part 3 of my series of posts dedicated to creating an animating and virtualizing WrapPanel for Silverlight. If you missed the previous posts, I suggest you take a look.
Setup
NumberBox Animation
Let’s make this easy on ourselves by having the child control handle it’s own animation. To do this I’ve added an animation to my NumberBox resources called "slide.”
<Storyboard x:Key="slide">
<DoubleAnimation To="0" BeginTime="0:0:0" Duration="0:0:0.5"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[0].(TranslateTransform.X)" />
<DoubleAnimation To="0" BeginTime="0:0:0" Duration="0:0:0.5"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[0].(TranslateTransform.Y)" />
</Storyboard>
You’ll notice there isn’t anything special about this Storyboard, it’s a simple TranslateTransform animation. Oh, make sure you’ve got your RenderTransform defined too.
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RenderTransform>
<TransformGroup>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Grid.RenderTransform>
I’ve created this animation as a resource for my UserControl, but it could just as easily live in the application level resource dictionary, or be created entirely in code behind. But, now that we’ve got the Storyboard in place, let’s give callers the ability to pick a destination and start the animation. For that I’ve created a public method which takes two parameters to define the translate transform values like this:
public void SlideTo(double x, double y)
{
Storyboard slideAni = Resources["slide"] as Storyboard;
(slideAni.Children[0] as DoubleAnimation).To = x;
(slideAni.Children[1] as DoubleAnimation).To = y;
slideAni.Begin();
}
ArrangeOverride
Animation
Now that we are going to animate the location of our panel’s children we will need to update the ArrangeOverride method. First of all, even though we are going to animate the location of the child control, the logic required to determine that location does not change. Secondly, we still need to call the UIElement.Arrange method for each control to ensure that it will be shown.
WrapPanel
Our WrapPanel will call the NumberBox.SlideTo method to animate the control. We do not ever set the From values for that animation, necessitating that the NumberBox always be Arranged to the same location and that each transform be relative to that same location. To keep things simple we will arrange every child to (0,0) and then animate the TranslateTransform. That makes our ArrangeOverride code look like this:
protected override Size ArrangeOverride(Size finalSize)
{
Size sizeSoFar = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));
(child as NumberBox).SlideTo(sizeSoFar.Width, sizeSoFar.Height);
if (sizeSoFar.Width + child.DesiredSize.Width >= finalSize.Width)
{
sizeSoFar.Height += child.DesiredSize.Height;
sizeSoFar.Width = 0;
}
else
{
sizeSoFar.Width += child.DesiredSize.Width;
}
}
return finalSize;
}
So, with a simple control-level animation and two changes to our ArrangeOverride we are now animating our WrapPanel! Easy!
Next Steps
This has been a simplistic example to prove we can animate our custom panel. A more realistic, real-world scenario is using our panel as the ItemsPanelTemplate of an ItemsControl bound to a collection of data objects. For the next post we will move to using an ItemsControl and discuss the need for and benefits of Virtualization.