Animated Polyline Interpolations in Silverlight
January 7, 2009
New York, N.Y.
In my Monday blog entry I used the CompositionTarget.Rendering event to animate an interpolation between two equally sized PointCollection objects.
I like CompositionTarget.Rendering: It takes all the guesswork out of deciding what period to use for timer animations. But whenever possible I think it's best to use the Timeline derivatives in Silverlight for animation. There are many advantages, not the least being that you can define them in XAML, and they are much more easily integratable with other animations.
That's the purpose of the PointCollectionInterpolator class that is part of the downloadable PolylineInterpolationDemo project.
PointCollectionInterpolator derives from FrameworkElement (I'll explain why shortly) and defines four dependency properties: Points1 and Points2 of type PointCollection, Progress of type double, and InterpolatedPoints, also of type PointCollection. Whenver one of the first three properties changes, the class executes the following code:
-
void OnChanged(DependencyPropertyChangedEventArgs args)
{
if (InterpolatedPoints == null ||
Points1 == null || Points2 == null)
return;
InterpolatedPoints.Clear();
int num = Math.Min(Points1.Count, Points2.Count);
for (int i = 0; i < num; i++)
InterpolatedPoints.Add(
new Point((1 - Progress) * Points1[i].X +
Progress * Points2[i].X,
(1 - Progress) * Points1[i].Y +
Progress * Points2[i].Y));
}
The PointCollectionInterpolator is instantiated as a resource (along with two PointCollection objects) in the Page.xaml file:
-
<UserControl x:Class="PolylineInterpolationDemo.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:PolylineInterpolationDemo">
<UserControl.Resources>
<PointCollection x:Key="yesPts">
5 11, 3 10, 2 8, 2 7, 3 5, 5 4, 6 4, 8 5,
9 7, 9 9, 8 13, 7 16, 6 20, 6 22, 7 24, 8 25,
10 25, 12 24, 14 22, 16 19, 17 17, 19 11, 21 4, 19 11,
16 21, 14 27, 12 32, 10 36, 8 37, 7 36, 7 34, 8 31,
10 28, 13 26, 17 25, 23 23, 25 22, 26 21, 27 19, 27 17,
26 16, 25 16, 23 17, 22 19, 22 22, 23 24, 25 25, 27 25,
29 24, 30 23, 32 20, 34 17, 35 15, 35 17, 37 20, 38 22,
38 24, 36 25, 32 24, 34 25, 38 25, 40 24, 41 23, 43 20
</PointCollection>
<PointCollection x:Key="noPts">
5 11, 3 10, 2 8, 2 7, 3 5, 5 4, 6 4, 8 5,
9 7, 9 8, 9 9, 8 14, 7 18, 5 25, 7 18, 10 10,
11 8, 12 6, 13 5, 15 4, 17 4, 19 5, 20 7, 20 8,
20 9, 19 14, 17 21, 17 24, 18 25, 19 25, 21 24, 22 23,
24 20, 25 18, 26 17, 28 16, 29 16, 30 16, 29 16, 28 16,
26 17, 25 18, 24 20, 24 21, 24 22, 25 24, 27 25, 28 25,
29 25, 31 24, 32 23, 33 21, 33 20, 33 19, 32 17, 30 16,
29 17, 29 18, 29 19, 30 21, 32 22, 35 22, 37 21, 38 20
</PointCollection>
<src:PointCollectionInterpolator
x:Key="interpolatorKey"
Name="interpolator"
Points1="{StaticResource yesPts}"
Points2="{StaticResource noPts}" />
</UserControl.Resources>
<Grid>
<Polyline Name="polyline"
Points="{Binding Source={StaticResource interpolatorKey},
Path=InterpolatedPoints}"
RenderTransform="10 0 0 10 0 0"
StrokeThickness="1"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"
StrokeLineJoin="Round">
<Polyline.Stroke>
<SolidColorBrush x:Name="brush" />
</Polyline.Stroke>
</Polyline>
</Grid>
<UserControl.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="interpolator"
Storyboard.TargetProperty="Progress"
From="0" To="1" Duration="0:0:3" />
<ColorAnimation Storyboard.TargetName="brush"
Storyboard.TargetProperty="Color"
From="Green" To="Red" Duration="0:0:3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</UserControl.Triggers>
</UserControl>
Notice that the PointCollectionInterpolator object is given both a resource x:Key and a Name. The x:Key allows the resource to be referenced in a binding (as usual) while the Name allows the Progress property to be animated by a DoubleAnimation. To celebrate the use of the DoubleAnimation for this job, I've also combined it with a ColorAnimation to change the polyline between green and red. You can see it running here:
PolylineInterpolationDemo.html
The availability of the Name property is the only reason I derived the PointCollectionInterpolator class from FrameworkElement rather than DependencyObject. You can't put an x:Name attribute in a resource but you can make use of Name. (I discussed issues with using x:Name and Name with WPF animations here and here.)
However, here's an interesting experiment you can perform: In PointCollectionInterpolator, change the base class from FrameworkElement to DependencyObject. The program compiles with a warning about "The property 'Name' does not exist on the type 'PointCollectionInterpolator'..." but the program works anyway! Silverlight sure has some strange voodoo inside!
The next step: Getting this polyline interpolation inside a ControlTemplate for a CheckBox. How disturbing it is to find that Silverlight templates don't have local resources....