Creating animations in code with Silverlight 1.1 (stand November 2007)
Introduction
In the current version of Silverlight 1.1, creating animations in code by creating a new Storyboard and one or more new (for example) ColorAnimation(s) is not possible. In WPF, you can do this, but Silverlight doesn't have this ability yet. Thankfully, there is a workaround involving string manipulation and the XamlReader class.
Creating the XAML code
In this example, we will create a ColorAnimation going from one random color to another random color in a random timespan. To keep it simple, we will simply create a new animation when the page is loaded. To see a different animation, you'll need to refresh the page using the F5 key in your web browser.
-
Start by creating a new Silverlight 1.1 project in Visual Studio 2008.
-
Then, open the file Page.xaml and place an Ellipse in the middle of the main Canvas:
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="SilverlightProject1.Page;assembly=ClientBin/SilverlightProject1.dll"
Width="640"
Height="480">
<Ellipse Width="320"
Height="240"
Canvas.Top="120"
Canvas.Left="160"
x:Name="Ellipse" />
</Canvas>
-
Then, we must create a ColorAnimation, which we will apply to the Ellipse. In order to test the code, we will create this animation in the XAML code first, and then copy and modify it in the C# code; first, add a "Canvas.Triggers" section to the XAML code, before the Ellipse:
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard Name="TestStoryboard"
AutoReverse="True"
RepeatBehavior="Forever">
<ColorAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="(Shape.Fill)
.(SolidColorBrush.Color)"
From="Red"
To="Green"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
If you run the code at this point (in Studio, press Ctrl-F5), you'll see the Ellipse's Fill being animated from Red to Green in 2 seconds, and back. The animation never stops, because we set the "RepeatBehavior" property to "Forever".
Passing the animation in code
-
Copy the animation code (from the opening "<Storyboard" tag to the closing "</Storyboard>" tag) into the C# code, and modify it as follows:
private const string ANIMATION = "<Storyboard Name=\"TestStoryboard\""
+ " AutoReverse=\"True\""
+ " RepeatBehavior=\"Forever\">"
+ "<ColorAnimation Storyboard.TargetName=\"Ellipse\""
+ " Storyboard.TargetProperty=\"(Shape.Fill).(SolidColorBrush.Color)\""
+ " From=\"{0}\""
+ " To=\"{1}\""
+ " Duration=\"{2}\"/>"
+ "</Storyboard>";
There are a few things you must take care of when you copy XAML code to the C# code file like we just did:
-
All the attributes quotes must be escaped, by adding a '\' (backslash) character in front of them. This is to signify to the compiler that this quote sign is not a C# string delimiter, but that it must simply be added to the XAML string.
-
Make sure that there are spaces before the attributes. Even though we placed the string on multiple lines for a better readibility, in the end the string will be all on one line when it is compiled. If you omit to place spaces, this will cause errors. For example, we write " AutoReverse=\"True\"", with a leading space. We could also add new lines instead of putting all on one single line, but this makes the code more difficult to read.
-
Replace all the elements you want to modify in the code with a "{X}" sequence, where X is a number. All the numbers must be unique. Later, we will use the string.Format method to replace these elements with a random value.
Note: At this stage, you can delete the whole "Canvas.Triggers" section from the XAML code.
Creating random values
The next step is to replace the random values in the animation string. We need three random values: a "From" color, a "To" color and a "Duration". For the "From" and the "To", we will create a new Color from scratch using the Color.FromRgb and Color.ToString methods, using a Random class to create the values:
Random random = new Random();
byte[] rgbFrom = new byte[3];
byte[] rgbTo = new byte[3];
random.NextBytes(rgbFrom);
random.NextBytes(rgbTo);
Color fromColor = Color.FromRgb(rgbFrom[0], rgbFrom[1], rgbFrom[2]);
Color toColor = Color.FromRgb(rgbTo[0], rgbTo[1], rgbTo[2]);
Then we want to construct a Timespan with a random duration, anywhere between 1 and 10 seconds.
TimeSpan duration = TimeSpan.FromSeconds(random.Next(1, 10));
Replacing the values
Eventually, we replace the values in the animation string, then construct the animation object.
string animation = string.Format(ANIMATION,
fromColor.ToString(),
toColor.ToString(),
duration.ToString());
Storyboard storyboard = XamlReader.Load(animation) as Storyboard;
Cleaning up
Before we can start the animation, there are three details we must take care of:
-
The storyboard must be added to the Canvas' resources before it is started. The reason is that the "TargetName" and the animation must both be in the same namescope.
-
After the storyboard is completed, it is good practice to stop it, and to remove it from the resources, allowing it to be discarded. Note: In our particular case, it's not strictly necessary, considering that the animation will stop only when the page is reloaded, when the user navigates away, or when the web browser is closed. Still, we will add this code to comply to the best practices.
-
And finally, we can start the animation.
storyboard.Completed += new EventHandler(stoyboard_Completed);
storyboard.Begin();
with:
void storyboard_Completed(object sender, EventArgs e)
{
(sender as Storyboard).Stop();
this.Resources.Remove(sender as DependencyObject);
}
Note that alternatively, you can also create the animations in XAML and then modify their properties in code, but that doesn't allow complex code, like animating objects created dynamically at runtime.
Conclusion
Fortunately, the CLR offers enough processing power (compared to JavaScript) to allow even complex animations using string manipulation. Using the objects directly instead of creating XAML strings and using the XamlReader would be faster, but this option doesn't exist in the moment. The technique shown here is a workaround until we get the same possibilities as we have in WPF.