Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2010 Grant Archibald
One of the original problems with the initial port of BabySmash to Silverlight was loading the XAML storyboard animations for UserControls.
In this post I will cover the changes required from a Silverlight point of view to replace the RoutedEvent in the UserControl.Triggers section with either equivalent XAML or C# code depending on your need.
XAML in WPF allows tools such as Expression Blend to define triggered events without the use of code. In the original BabySmash XAML similar the following exists, which starts a storyboard when the user control is loaded e.g.
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource EyesSB}"/>
</EventTrigger>
</UserControl.Triggers>
As Shawn Wildermuth indicated on the Silverlight forums Routed Events are not available in Silverlight 2.0 Beta 2 as a result the event trigger either needs to be deleted or commented out in the XAML and alternative approach taken to achieve the same result.
Looking Richard Griffen's Silverlight Tweening Adventures with Baby Smash! MyTweenerTest it shows that the XAML can easily be refactored by moving the animations inside a BeginStoryBoard element. Hence not require the use of the Loaded event e.g.
<UserControl.Resources>
<BeginStoryboard x:Name="Begin">
<Storyboard x:Name="EyesSB" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CircleEye1" Storyboard.TargetProperty="(UIElement.Opacity)" RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.1000000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.300000" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:7.300000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CircleEye2" Storyboard.TargetProperty="(UIElement.Opacity)" RepeatBehavior="Forever">
</Storyboard>
</BeginStoryboard>
</UserControl.Resources>
However I wanted to look at a way of using the code to apply dynamic changes to the XAML and walkthough the process of refactoring.
In the next few steps I will introduce a series of refactorings that create the code behind and them reduce the amount of code that needs to be repeated.
Overall the aim of the refactorings are to:
Overall the effect is that the Routed event section is no longer requires in each of the XAML files.
Change the code behind for each of the shapes and add a ShapedLoaded event e.g.
public CoolHeart()
{
this.InitializeComponent();
this.Loaded += ShapeLoaded;
}
private void ShapeLoaded(object sender, RoutedEventArgs e)
var eyes = FindName("EyesSB") as Storyboard;
if (eyes != null)
eyes.Begin();
To aid with readability and reuse across all the shapes the refactoring above can further be improved. By moving to an extension method in a separate class. As a result we can remove ShapeLoaded from each shape e.g.
this.Loaded += this.BeginStoryBoardAnimation("EyesSB");
With the definition of the extension method as follows
/// <summary>
/// Helper class for XAML that performs common tasks
/// </summary>
public static class XamlHelper
/// Begins the named storyboard animation.
/// <remarks>No exception if thrown if the storyboard is not found</remarks>
/// <param name="control">The control that contains the storyboard</param>
/// <param name="storyboardName">Name of the storyboard to be started</param>
/// <returns>An event handler instance </returns>
public static RoutedEventHandler BeginStoryBoardAnimation(this UserControl control, string storyboardName)
return (source, args) =>
var eyes = control.FindName(storyboardName) as Storyboard;
};
Remove the initialisation of the Loaded event from the constructor of each shape and update the FigureGenerator so that the EyesSB is started if it exists.
public static UserControl NewUserControlFrom(FigureTemplate template)
UserControl retVal = null;
//We'll wait for Hardware Accelerated Shader Effects in SP1
if (template.Letter.Length == 1 && Char.IsLetterOrDigit(template.Letter[0]))
retVal = new CoolLetter(template.Fill, template.Letter);
else
retVal = template.GeneratorFunc(template.Fill);
retVal.StartStoryboardAnimation("EyesSB");
With the updated definition of the extension method
/// Begins the named story board animation.
/// <remarks>No exception if thrown if the story board is not found</remarks>
/// <param name="control">The control that contains the story board</param>
return (source, args) => StartStoryboardAnimation(control, storyboardName);
/// Starts the named story board animation.
public static void StartStoryboardAnimation(this UserControl control, string storyboardName)
Comments [0] Friday, August 08, 2008 2:10:40 AM (GMT Standard Time, UTC+00:00) Related posts:Three Little Pigs – Silverlight E-BookMy Mix09 10k Contest Entry Is Live – Spin And WinSilverlight BabySmash Audio FilesSilverlight BabySmash Performance – The Asynchronous StoryBabySmash At PDCTracking Silverlight And Moonlight Enabled Browsers via Google Analytics silverlight | babysmash