Transforms
As you've already learned, Silverlight animations work by modifying the value of a property. Elements have several properties that can be usefully changed. For example, you can use Canvas.Left and Canvas.Top to move an element around. Or, you can alter the Opacity setting to make an element fade into or out of view. However, it's not immediately clear how you can perform more exciting alterations, like rotations.
The secret is "transforms." A transform is an object that alters the way a shape or other element is drawn by shifting the coordinate system it uses. You can use transforms to stretch, rotate, skew, and otherwise manipulate the shapes, images, and text in your Silverlight user interface. Transforms are useful for getting the right shape you want, but they're even more interesting when you're animating. By animating a property in a transform, you can rotate a shape, move it from one place to another, or warp it dynamically.
Table 2 lists the transforms that are supported in Silverlight.
TranslateTransform | Displaces your coordinate system by some amount. This transform is useful if you want to draw the same shape in different places. | X,Y |
RotateTransform | Rotates your coordinate system. The shapes you draw normally are turned around a center point you choose. | Angle, CenterX, CenterY | ScaleTransform | Scales your coordinate system up or down so that your shapes are drawn smaller or larger. You can apply different degrees of scaling in the X and Y dimensions, thereby stretching or compressing your shape. | ScaleX, ScaleY, CenterX, CenterY | SkewTransform | Warps your coordinate system by slanting it a number of degrees. For example, if you draw a square, it becomes a parallelogram. | AngleX, AngleY, CenterX, CenterY | MatrixTransform | Modifies your coordinate system using matrix multiplication with the matrix you supply. This is the most complex option -- it requires some mathematical skill. | Matrix | TransformGroup | Combines multiple transforms so they can all be applied at once. The order in which you apply transformations is important -- it affects the final result. For example, rotating a shape (with RotateTransform) and then moving it (with TranslateTransform) sends the shape off in a different direction than if you move it and then rotate it. | N/A |
Technically, all transforms use matrix math to alter the coordinates of your shape. However, using prebuilt transforms such as TranslateTransform, RotateTransform, ScaleTransform, and SkewTransform is far simpler than using the MatrixTransform and trying to work out the right matrix for the operation you want to perform. When you perform a series of transforms with TransformGroup, Silverlight fuses your transforms together into a single MatrixTransform, ensuring optimal performance.
Using a Transform
To transform an element, you set its RenderTransform property with the transform object you want to use. Depending on the transform object you're using, you'll need to fill in different properties to configure it, as detailed in Table 2.
For example, if you're rotating a shape, you need to use the RotateTransform and supply the angle in degrees. Here's an example that rotates a square clockwise by 25 degrees:
<Rectangle Width="80" Height="10" Stroke="Blue" Fill="Yellow" Canvas.Left="100" Canvas.Top="100"> <Rectangle.RenderTransform> <RotateTransform Angle="25" /> </Rectangle.RenderTransform> </Rectangle>
When you rotate a shape in this way, you rotate it about the shape's origin (the top-left corner). If you want to rotate a shape around a different point, you can use the handy RenderTransformOrigin property. This property sets the center point using a proportional coordinate system that stretches from 0 to 1 in both dimensions. In other words, the point (0, 0) is designated as the top-left corner, and (1, 1) is the bottom-right corner. (If the shape region isn't square, the coordinate system is stretched accordingly.)
With the help of the RenderTransformOrigin property, you can rotate any shape around its center point using markup like this:
<Rectangle Width="80" Height="10" Stroke="Blue" Fill="Yellow" Canvas.Left="100" Canvas.Top="100" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <RotateTransform Angle="25" /> </Rectangle.RenderTransform> </Rectangle>
Animating a Transform
To use a transform in animation, the first step is to define the transform. (An animation can change an existing transform but not create a new one.) For example, imagine you want to allow a rectangle to rotate. This requires RotateTransform:
<Rectangle x:Name="rect" Width="80" Height="50" Stroke="Blue" Fill="Yellow" Canvas.Left="100" Canvas.Top="100" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <RotateTransform></RotateTransform> </Rectangle.RenderTransform> </Rectangle>
Now here's a storyboard that makes the rectangle rotate when the mouse moves over it. It uses the target property (UIElement.RenderTransform).Angle -- in other words, it reads the RenderTransform property of the Rectangle and modifies the Angle property of the RotateTransform object that's defined there. The fact that the RenderTransform property can hold a variety of different transform objects, each with different properties, doesn't cause a problem. As long as you're using a transform that has an angle property, this trigger will work.
<Rectangle x:Name="rect" Width="80" Height="50" Stroke="Blue" Fill="Yellow" Canvas.Left="100" Canvas.Top="100" RenderTransformOrigin="0.5,0.5" MouseEnter="rect_Enter"> <Rectangle.RenderTransform> <RotateTransform></RotateTransform> </Rectangle.RenderTransform> <Rectangle.Resources> <Storyboard x:Name="rotateStoryboard"> <DoubleAnimation Storyboard.TargetName="rect" Storyboard.TargetProperty="(UIElement.RenderTransform).Angle" To="360" Duration="0:0:0.8" RepeatBehavior="Forever"></DoubleAnimation> </Storyboard> </Rectangle.Resources> </Rectangle>
Finally, an event handler starts the storyboard:
private void rect_Enter(object o, EventArgs e) { rotateStoryboard.Begin(); }
The rectangle rotates one revolution every 0.8 seconds and continues rotating perpetually. While the rectangle is rotating, it's still completely usable -- for example, it still raises the
To stop the rotation, you can use a second trigger that responds to the MouseLeave event. At this point, you could call the Storyboard.Stop() method, but this will cause the button to jump back to its original orientation in one step. A better approach is to start a second animation that replaces the first. Here's how the second animation is defined:
This animation seamlessly rotates the rectangle back to its original orientation in a snappy 0.2 seconds. You can place this storyboard in the same Rectangle.Resources collection as the first animation.
All you need to do is attach an event handler to the Rectangle.MouseLeave event that runs the storyboard:
<Storyboard x:Name="revertStoryboard">
<DoubleAnimation
Storyboard.TargetName="rect"
Storyboard.TargetProperty="(UIElement.RenderTransform).Angle"
To="0" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
private void rect_Leave(object o, EventArgs e)
{
revertStoryboard.Begin();
}