DevX HomePage

Leveraging Your Flash Development with Silverlight

You're not giving up Flash any time soon (and we don't blame you.) But if you could get your Flash application working in Silverlight, why wouldn't you? We show you the tools and techniques required to have your rockin' Flash application rolled for Silverlight.

Recasting your Flash application for Microsoft® Silverlight™ 2.0 opens up your project to .NET developers and gives you the option to put managed code behind the carefully-designed interface elements that your users already know and love. In this article, I'll discuss porting a sample application from Flash to Silverlight, focusing on how you might transfer movie clips and other assets. There isn't an automated process or a developer's stone that can transmute a Flash movie into a Silverlight assembly (or vice-versa), so we'll have to take a manual approach. The application we end up with will look almost exactly like the Flash version, but will be pure Silverlight, built on XAML and C#.

Figure 1. Copter as a Flash Movie

Introducing Copter
We'll port a sample Flash movie I built for this article. The application includes a helicopter with animated rotors that you can move up, down, left, or right by clicking the control buttons (Figure 1).

The helicopter is a movie clip that is tweened in the appropriate direction when a button is clicked. The buttons are Flash button controls, and the button events are handled with a small amount of ActionScript, using the continueTo() method of the Tween class. This snippet, for example, moves the clip 40 pixels up in one second each time the button is clicked:

 Function moveUp(event:MouseEvent):void {
	yTween.continueTo( copterMc.y-40, 1);
}

Finally, there is a bitmap scenery image that is simply dropped into the background layer.

A Time for Tools
Our first challenge is to get the copter movie clip over to Silverlight. The most straightforward (and most high-fidelity) way to do this is to use a tool to extract the vector image information from the SWF movie and convert it to XAML. There are both commercial (theConverted) and non-commercial (Michael Swanson's SWF2XAML) tools available now, and a few companies have additional converters in the works. None of these tools claim a completely automated conversion (neither theConverted nor SWF2XAML does video, for example).

I chose SWF2XAML for this project, because it's not commercial and because it works well for what I need, which is to extract a movie clip. You'll need to review the available tools to see which is best for your project.

You point SWF2XAML at the SWF movie. It renders the movie frame-by-frame, allowing you to step through the movie or to export each individual frame as XAML (Figure 2).

Figure 2. The Copter in SWF2XAML

SWF2XAML will export the XAML for every layer in the movie, but we're only interested in extracting the embedded copter movie clip. So, we turn off the other items in the display list and step through the movie. The copter movie clip has only 12 frames, so we save the XAML from the first 12 frames only. When we're done, SWF2XAML has produced 12 files, each a complete XAML representation of a frame from the copter movie clip.

Each file that SWF2XAML produces is a collection of Path or drawing objects that are in turn collected into XAML Canvas objects that represent layers. For the copter movie clip, one layer has nothing but static paths, while the other layer contains the two paths that are animated (the main rotor and the tail rotor of the helicopter). We can just put the canvas containing the static paths directly into the Page.xaml file of our new ported Silverlight project.

Here's what the beginning of that canvas looks like:

 <Canvas Name="copterMc" RenderTransform="1,0,0,1,321,138"> 
    <Canvas>
      <Canvas>
        <Path Fill="#FFFF4E22" Data="M-206.7,-58.55L-187.1,-24.65L-195.2,
-24.7L-214.8,-58.6L-206.7,-58.55" /> <Path Fill="#FFFF4E22" Data="M-187.65,-24.4L-193.65,-2L-200.7,-3.9L
-194.7,-26.3L-187.65,-24.4" /> <Path Fill="#FF000000" Data="M-59.55,21.85L-66.9,34.55L-70.25,32.65L
-62.9,19.95L-59.55,21.85" />

SWF2XAML gave us the RenderTransform that will make the images appear in the right place on screen, so all we should have to do is paste in the path definitions. But there are a few other steps. SWF2XAML generates Path elements that look like this:

 <Path Fill="# FF000000">
    <Path.Data>
        <PathGeometry ="M-59.55,21.85L-66.9,34.55L-70.25,32.65L 62.9,19.95L
-59.55,21.85" /> </Path.Data> </Path>

which the current Silverlight alpha will not accept. PathGeometry is a valid XAML fragment and would work with WPF, but it doesn't work with Silverlight at this time (that may change in the future). So we also need to convert each path to a stream geometry format, which will load properly:

<Path Fill="#FF000000" Data="M-59.55,21.85L-66.9,34.55L-70.25,32.65L-62.9,19.95L-
59.55,21.85" />

We now have the static parts of the copter in a XAML format that works with Silverlight. But our animated segments are just lists of paths; we can't use Silverlight animations to represent these. Instead, we'll need to build a timer that can walk through the list and set the path geometries for each frame.

As a first step, we'll compile the dynamic paths from the 12 XAML files into two string arrays, one for each path that we need to animate (the two rotors). Here's the first part of the main rotor array initialization:

 string[] mainRotorGeometries = new string[] {
"M67.2,-41L-43.85,-41L-43.85,-44.5L67.2,-44.5L67.2,-41 M-157.45,
-44.5L-46.4,-44.5L-46.4,-41L-157.45,-41L-
157.45,-44.5",

Now we need a timer. We can create a timer in XAML using a storyboard1, like this:

<Canvas.Resources>
    <Storyboard x:Name="AnimationTimer" Completed="AnimationTimerCompleted"
Duration="00:00:00.10" /> </Canvas.Resources>

This creates a one-shot storyboard animation that doesn't actually do any animating but just fires the AnimationTimerCompleted event after 1/10 of a second. In the timer event handler, we step through the arrays of path geometries, assigning each to the appropriate path in turn (Listing 1). frameIndex is a private variable of the Page that is initialized in the constructor.

Listing 1. The animation timer event handler.

void AnimationTimerCompleted(object sender, EventArgs e)
{
    // get the next set of geometry strings.
    string tailRotorGeometry = tailRotorGeometries[this.frameIndex];
    string mainRotorGeometry = mainRotorGeometries[this.frameIndex];

    // set the path geometry for the main rotor path and the tail rotor path.
    tailRotor.SetValue(Path.DataProperty, tailRotorGeometry);
mainRotor.SetValue(Path.DataProperty, mainRotorGeometry); // set up the next frame index. this.frameIndex++; if (this.frameIndex > tailRotorGeometries.Length - 1) { this.frameIndex = 0; } // restart the timer now. AnimationTimer.Begin(); }

At the end of the timer handler, we fire off the timer animation again so that our handler gets called continuously.

Now we have an animated helicopter on our main page, and it looks exactly like the Flash version. That's progress, but we'd prefer the Silverlight image to be more like the Flash movie clip it came from: self-contained and reusable.


1 For more information on using storyboards as timers, see this entry from Joe Stegman's WebBlog.




Timer-less Animation
The first step is to replace the timer-based animation with a storyboard animation. The timer-based animation is a faithful frame-by-frame representation of the Flash clip, but we can come up with a very good alternative that will be easier to work with using storyboards.

We can throw out the timer and the geometry lists, keeping only the dynamic paths from the first frame. Then we'll animate both of the paths using a rotation transform. First, we define a storyboard for the tail rotor animation, which spins in place (Listing 2).

Listing 2. The storyboard animation, which starts automatically when the canvas is loaded.

<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
    <BeginStoryboard>
	<Storyboard x:Name="Motion">
	    <DoubleAnimation
		Storyboard.TargetName="tailRotorTransform"
		Storyboard.TargetProperty="(RotateTransform.Angle)"
		From="0.0" To="360" Duration="0:0:.05"
		RepeatBehavior="Forever"/>
	</Storyboard>
    </BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>

Listing 2 adds a trigger to the canvas to start up the storyboard animation on load. The animation spins the tail rotor by animating the angle of the rotate transform through 360 degrees.

The rotate transform is added under the tail rotor path element and looks like this:

<Path.RenderTransform>
    <RotateTransform x:Name="tailRotorTransform" Angle="0" CenterX="-192.6" 
CenterY="-26.1" /> </Path.RenderTransform>

The CenterX and CenterY attributes move the center of the rotation to the center of the tail rotor, keeping it spinning in place. We can "rock" the main rotor with an animation similar to the one we used for the tail rotor, but that goes through a smaller angle and auto-reverses after each cycle.

We've now got the copter clip looking pretty good, and it's now entirely in XAML, with no dependencies on C# code. But it's still on the main page, which isn't very much like the library-based movie clip in the Flash application. The next step is to move it into a Silverlight user control.

Converting to a User Control
In Flash, the movie clip lives in the library, and it can easily be pulled out and used in different contexts. That's not true of the Silverlight version, at least not of the way we've constructed it initially. It would be messy, for example, to create two helicopters instead of one. Wrapping the copter up in a user control gives us the kind of reusability we enjoyed with the Flash movie clip.

It's pretty easy to make the conversion to a user control with Microsoft® Silverlight™ Tools Beta for Visual Studio® 2008. You just pick the "Silverlight User Control" template off the list, and Visual Studio 2008 drops in a new control XAML file and control C# code. The template C# code takes care of control-specific requirements, which amounts to finding the XAML file in the assembly and creating the control using InitializeFromXaml.

Now that we have a template, we can migrate most of the XAML from our current Page.xaml over to the new control. All we've done in the main page to this point is work on the movie clip, so moving the copter movie clip over to the control XAML is a cut-and-paste operation. We now have a nice self-contained, XAML-based movie clip, which appears in its entirety in Listing 3.

Listing 3. The CopterControl.Xaml file.

<Canvas
	xmlns="http://schemas.microsoft.com/client/2007"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Width="100" Height="50"
	xmlns:CopterSL="clr-namespace:CopterSL;assembly=ClientBin/CopterSL.dll"
	>

  <Canvas.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded">
      <BeginStoryboard>
        <Storyboard x:Name="Motion">
          <DoubleAnimation
              Storyboard.TargetName="tailRotorTransform"
              Storyboard.TargetProperty="(RotateTransform.Angle)"
              From="0.0" To="360" Duration="0:0:.05"
              RepeatBehavior="Forever"/>
          <DoubleAnimation
              Storyboard.TargetName="mainRotorTransform"
              Storyboard.TargetProperty="(RotateTransform.Angle)"
              From="-2.5" To="2.5" Duration="0:0:2"
              RepeatBehavior="Forever"
              AutoReverse="True"/>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Canvas.Triggers>

  <Canvas Name="copterMc" RenderTransform="1,0,0,1,321,138">
    <Canvas>
      <Path Fill="#FFFF4E22" Data="M-195,-26Q-189.95,-29.2,-118,-27.95Q-81.3,-27.3,-38.5,
-26Q-34.65,-26,-33.25,-21.45Q-32.3,-18.35,-32.2,-11.1L-31.85,0.25Q-31.1,6,-28.45,7.7Q-26.75,
8.75,-22.8,12.25Q-19.35,15.2,-17.15,16.25Q-10.25,19.5,-1.15,11.5Q0.8,9.75,1.05,9.95Q1.3,
10.1,-0.05,11.65Q-3.75,15.8,-9.15,19.25Q-21.3,27,-38.5,27Q-55.7,27,-67.85,19.25Q-72.2,16.5,
-74.7,9.5Q-77.3,2.4,-80,0.5Q-96.1,-10.7,-151.05,-18.15L-184.4,-22.45Q-197.6,-24.35,-195,-26"/> <Path Fill="#FFFF4E22" Data="M-89.5,-23L-89.5,-29Q-89.5,-32.3,-87.15,-34.65Q-84.8,-37,-81.5,
-37L-35,-37Q-31.7,-37,-29.35,-34.65Q-27,-32.3,-27,-29L-31.1,-29L-32.7,-27Q-33.15,-26.1,-33,
-24.65Q-31.9,-15.2,-34,-15.2L-81.5,-15Q-84.8,-15,-87.15,-17.35Q-89.5,-19.7,-89.5,-23" /> <Path Fill="#FFFF4E22" Data="M-206.7,-58.55L-187.1,-24.65L-195.2,-24.7L-214.8,
-58.6L-206.7,-58.55" /> <Path Fill="#FFFF4E22" Data="M-187.65,-24.4L-193.65,-2L-200.7,-3.9L-194.7,-26.3L-187.65,-24.4" /> <Path Fill="#FF000000" Data="M-59.55,21.85L-66.9,34.55L-70.25,32.65L-62.9,19.95L-59.55,21.85" /> <Path Fill="#FF000000" Data="M-22.4,22.4L-15,35.25L-17.7,36.8L-25.1,23.95L-22.4,22.4" /> <Path Fill="#FF000000" Data="M7,36.5L-82.5,36.5L-82.5,32L7,32L7,36.5" /> <Path Fill="#B366CCFF" Data="M-26.1,-29.05Q-20.75,-28.6,-17.85,-27.4Q-13.4,-25.55,-11.05,
-23.3L-6.55,-19.25Q-3,-16.75,-0.15,-12.35Q2.4,-8.4,3.35,-4.5Q4.6,0.4,3.95,4.35Q3.25,8.45,
0.65,11Q-2.2,13.9,-5.5,15.65Q-9.2,17.6,-12.7,17.6Q-16.05,17.6,-22.2,13.6Q-27.3,10.3,-30.65,
6.95Q-32.9,4.7,-33.3,-2.55Q-33.5,-6.45,-33.1,-13.15Q-33.05,-13.95,-33.95,-21.45Q-34.55,
-26.4,-32.7,-28.4Q-31.65,-29.55,-26.1,-29.05" /> <Path Fill="#FFFFFF00" Data="M-174.25,-25.5L-89.5,-25.5L-89.5,-23L-174.25,-23L-174.25,-25.5" /> <Path x:Name="mainRotor" Fill="#FF000000" Data="M67.2,-41L-43.85,-41L-43.85,
-44.5L67.2,-44.5L67.2,-41 M-157.45,-44.5L-46.4,-44.5L-46.4,-41L-157.45,-41L-157.45,-44.5"> <Path.RenderTransform> <RotateTransform x:Name="mainRotorTransform" Angle="0" CenterX="-46" CenterY="-41" /> </Path.RenderTransform> </Path> <Path x:Name="tailRotor" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Data="M-193.9
,-53.5L-191.4,-26.7L-195.9,-25.45L-197.85,-51.95L-193.9,-53.5 M-190.9,-23.45L-188.4,3.35L-192.9,
4.6L-194.85,-21.9L-190.9,-23.45"> <Path.RenderTransform> <RotateTransform x:Name="tailRotorTransform" Angle="0" CenterX="-192.6" CenterY="-26.1" /> </Path.RenderTransform> </Path> </Canvas> </Canvas> </Canvas>

We can put the newly-packaged control back on the main page with just one line of XAML:

<CopterSL:CopterControl x:Name="Copter" Canvas.Left="0" Canvas.Top="0" />

That was so easy, we might as well take care of adding in the bitmap background now, too. We have the bitmap that we imported into the original Flash application, so we can just pull that directly into Silverlight:

<Canvas.Background>
        <ImageBrush ImageSource="images/scenery.jpg" Stretch="None" AlignmentY="Top"/>
    </Canvas.Background>

The Stretch and AlignmentY attributes override the defaults to put the image at the top left, where it was in the original Flash project.

Adding Buttons
Flash has built-in button components, but there's no such thing as a standard Silverlight button control at this time2. We'll need to create our own button controls to fill in for the Flash buttons on the Copter application.

We've seen how to make movie-clip types of user controls. Creating button controls is a little more difficult, first because we'll want to create them in an external assembly so that they can be used in other projects, and second because there is additional work involved in setting up all the graphic elements that make up a button.

I built a simple button control with an upstate, a down state, and a mouseover state that I used with this project. The button uses storyboard animations for the state change effects. Those buttons are included with the sample source code, but I won't go into detailed explanations here. You can find excellent resources for creating custom button controls in the Silverlight QuickStarts and on Tim Heuer's blog.

The sample button class I built is called MyButton and the assembly is mapped into the "my" namespace. We can now add the four buttons to the Silverlight version of copter, just as they appear in the Flash version:

<Canvas x:Name="controls">
<my:MyButton x:Name="UpButton" MouseLeftButtonUp="OnUpButton" Label="Up" 
Canvas.Left="212" Canvas.Top="312.4"/> <my:MyButton x:Name="DownButton" MouseLeftButtonUp="OnDownButton" Label="Down"
Canvas.Left="212" Canvas.Top="364.3"/> <my:MyButton x:Name="LeftButton" MouseLeftButtonUp="OnLeftButton" Label="Left"
Canvas.Left="131.3" Canvas.Top="338.0"/> <my:MyButton x:Name="RightButton" MouseLeftButtonUp="OnRightButton" Label="Right"
Canvas.Left="298.3" Canvas.Top="338.0"/> </Canvas>

As you can see, we've specified the button event handlers directly in the XAML. All that's left is to implement the button actions.

Final Steps
In Flash, I used Tween3 objects to create the animation when the control buttons are clicked. The Tween class allows you to specify an easing function that contributes to more natural-looking motion. I'd like to be able to do the same in the Silverlight port.

Microsoft Expression® Blend lets you specify easing parameters on keyframe animations. I used Expression Blend to generate animations with keyframe easing, and then applied these to the custom CopterControl to animate it. The up button storyboard, which is kicked off by a button event, is shown in Listing 4.

Listing 4. The AnimateUp storyboard animation

<Storyboard x:Name="AnimateUp">
    <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CopterCanvas" 
Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)"> <SplinePointKeyFrame KeyTime="00:00:01" Value="0.5,0.5" KeySpline="0,0,0.75,1"/> </PointAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CopterCanvas"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].
(TranslateTransform.Y)"> <SplineDoubleKeyFrame x:Name="AnimateUpKeyFrame" KeyTime="00:00:01"
Value="-100" KeySpline="0,0,0.75,1"/> </DoubleAnimationUsingKeyFrames> </Storyboard>

Figure 3. The Silverlight Version

If you look carefully at Listing 4, you'll notice that the target is not the CustomCopter itself, but a canvas. The animation that Blend creates expects the target to have a RenderTransform of a certain type, which the CustomCopter lacks. One generic solution to this is to wrap the control in a canvas which matches Blend's requirements.

I also resorted to a second trick to get the kind of relative animation that we had in response to the Flash buttons. Since Silverlight's DoubleAnimationUsingKeyFrames doesn't allow relative value changes, I instead track the coordinates in C# code and update the Value property of the SplineDoubleKeyFrame before starting each animation.

Figure 3 shows the completed port. Moving to Silverlight not only gives us a new deployment platform, but gives us the .NET framework as a new development platform as well. That brings with it some terrific advantages, including flexible development with CLR languages, manipulating the DOM from managed code, a significant portion of the .NET library, and Visual Studio for an IDE.

Every project will be a little different, but some of the tasks we walked through on this sample, such as:

will probably be on your to-do list as you get your Flash application running under Silverlight.

More Resources


2 Scott Guthrie says that Silverlight controls will be included in an upcoming release.
3 Flash's Tween class supports easing and allows you to animate movie clip properties. The class is documented here.

* This article was commissioned by and prepared for Microsoft Corporation. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.


Steve Apiki is senior developer at Appropriate Solutions, Inc., a Peterborough, NH consulting firm that builds server-based software solutions for a wide variety of platforms using an equally wide variety of tools. Steve has been writing about software and technology for over 15 years.