Reader Level:
ARTICLE

Dealing with transformations in WPF: Part I - Standard 2D Transformations

Posted by Bechir Bejaoui Articles | WPF May 10, 2010
In WPF there are more than one transformation, I will try to present them the 2D ones one by one including some useful related techniques.
  • 1
  • 0
  • 9501
Download Files:
 

I-Introduction

"Transformations are like the spices that one uses to enhance the taste of a given dish" 

Just for kidding! As I said in the above proverb, transformations are very useful to build a rich interactive application that attracts the user. In WPF there are more than one transformation, I will try to present them the 2D ones one by one including some useful related techniques.

But first let's see which are objects those are concerned by transformations and what kind of transformations are applied to them.

II-Objects concerned by transformations

i.1 The UIElement: 

This is one of the user controls first rank ancestors, it represents a unique transform which is the RenderTransform one. It helps apply a transformation by shifting,rotating, skewing  that element. We will see later different techniques to leverage that.  

ii.2 The FrameworkElement:

It comes after the UIElement class and it presents two kinds of transformations, the first one is the RenderTransform which is inherited from the UIElement class and the second one is the LayoutTransform. The difference between both is fundamental. 

One major difference is in fact that the RenderTransform will be used to transform an object from a state to another one without taking in consideration whether the given object exceeds the layout container boundaries or not. 

At the contrast of the RenderTransform, the LayoutTransform will take the layout container dimensions in the consideration and will adjust the dimensions transformed object and the layout container to always keep the object within the layout container boundaries.

This is an example of the render transform

1.gif
 
Figure1

And then this is the same example but applying a LayoutTransform instead

2.gif
 
Figure2

In the figure 2 you can see that the rectangle dimensions are recalculated at the contrast of the figure 1 case. 

Then the question is in which case do I choose the first one over the second one? The same question for the opposite situation could also be asked! Well, it depends on the use case but keep in mind that the Layout transform is more resources consuming. 

 ii.3 The Geometry:

The geometry is an abstract class that represents the geometries family like the EllipseGeometry, the RectangleGeometry and rest of classes that inherits from the Geometry class and ends with the Geometry suffix. The characteristic of this family of objects is in fact that they could'nt render themselves as the Framework elements do. I mean, the Ellipse class which is of type Shape that is a direct descendant of the FrameworkElement can render itself but its equivalent the EllipseGeometry couldn't do. In this case, the LayoutTransform and the RenderTransform couldn't be applied, but in spite of this fact, the Geometry family makes use of the Transform property. This last one is used instead of the LayoutTransform and the RenderTransfom. Indeed, the LayoutTransform and the RenderTransform are enhanced versions of the Transform class, the first ones include the rendering faculties.

To understand the difference between Shapes which are of type FrameworkElement and the geometries you can refer to this article


ii.4 The TextEffect :

The TextEffect is a smart class that enables adding effects of editable elements such as TextBlock, TextBox controls and Flowdocuments such as animations and transformations. Like the Geometry case. The TextEffect makes use of a Transform property.

ii.5 The ContainerVisual:

The container visual is a sort of recipient that holds a collection of visual elements. It exposes a Transform property. In this case the transformation is applied to that collection as a unique composite block of visuals. 

ii.6 The drawing group: 

Like the visual, it is a sort of container but of different type of objects those are drawing ones, this class also exposes the Transform property that could be applied to the group as a unique structure. 

ii.7 The Brush:

First of all, we have to distinguish between the System.Drawing.Brush and System.Windows.Media.Brush, the last one is the type that we want to talk about. In fact, this last one exposes two transformations related properties, namely the well known Transform one and an additional one which is the RelativeTransform one. This last one is a coordinate related one. 

This kind of transform is used to perform a transformation related to a given point like a center or a focal without knowing the size or the dimension of the drawn area or independently of the drawn area. To well understand the issue here is a use case

Try to apply this XAML code:

<Ellipse>
    <Ellipse.Fill>
        <RadialGradientBrush>
            <RadialGradientBrush.RelativeTransform>
                <TranslateTransform X ="0.3" Y ="0.1"/>
            </RadialGradientBrush.RelativeTransform>
            <GradientStop Color="#850039d6" Offset ="0"/>
            <GradientStop Color="#654577Ff" Offset ="0"/>
            <GradientStop Color="#850039d6" Offset ="0.33"/>
            <GradientStop Color="#654577FF" Offset ="0.66"/>
            <GradientStop Color="#850039D6" Offset ="1"/>
        </RadialGradientBrush>
    </Ellipse.Fill>
</Ellipse>  

It will result the following output

3.gif
 
As you can recall, the center of the radian brush is shifted according to the translation. 

<RadialGradientBrush.RelativeTransform>
    <TranslateTransform X ="0.3" Y ="0.1"/>
</RadialGradientBrush.RelativeTransform>

III-Different types of transformations

TanslateTransform:

This is the most basic one, and the unique one that doesn't have a center. It only shifts a given elements according to the X or Y axes. To see what happens when shifting a rectangle over the X axis try this piece of XAML
<Grid>
<Rectangle Name="rectangle" Fill="#FFE42222" Stroke="Black" HorizontalAlignment="Left" Margin="8,105,0,159" Width="146" RenderTransformOrigin="0.5,0.5">
    <Rectangle.RenderTransform>
        <TranslateTransform x:Name="transform"/>
    </Rectangle.RenderTransform>
    <Rectangle.Effect>
        <DropShadowEffect BlurRadius="18"/>
        </Rectangle.Effect>
        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <BeginStoryboard>
                <Storyboard>
                         <DoubleAnimation From="0" To="250"  
                               Storyboard.TargetName="transform"
                               Storyboard.TargetProperty="X"/>
                    </Storyboard>
                    </BeginStoryboard>
            </EventTrigger>
        </Rectangle.Triggers>
</Rectangle>
</Grid>

To translate the same object according to the Y axis then replace the animated property X by the Y one at the StoryBoard.TargetProperty level. You have to recall that the TranslateTransform is not applicable perfectly within the LayoutTransform mode as the transformation will take in consideration the layout boundaries of the container. 

RotateTransform:

The RotateTransform is the second kind of transformation. It has a Center and Angle properties. The Center represents the rotation center and the angle represents the rotation angle. This piece of XAML represents a rotation transformation scenario. 

<Grid>
    <Rectangle Name="rectangle" Fill="#FFE42222" Stroke="Black" HorizontalAlignment="Left" Margin="8,86,0,0" Width="146" RenderTransformOrigin="0.5,0.5" Height="63" VerticalAlignment="Top">
        <Rectangle.RenderTransform>
            <RotateTransform x:Name="transform" CenterX="0" CenterY="0"/>
        </Rectangle.RenderTransform>
        <Rectangle.Effect>
            <DropShadowEffect BlurRadius="18"/>
        </Rectangle.Effect>
        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation From="0" To="360" 
                                        Storyboard.TargetName="transform"
                                        Storyboard.TargetProperty="Angle"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Rectangle.Triggers>
    </Rectangle>
</Grid>

ScaleTransform:

The ScaleTransform is used to change the dimension of a given visual like multiplying the width and the height of a given rectangle. 

It has ScaleX and ScaleY properties those are respectively related to the width and the height of the given visual. In addition to those properties there are CenterX and CenterY where they play the role of visual petitioners this once.

<Grid>
    <Rectangle Name="rectangle" Fill="#FFE42222" Stroke="Black" Margin="0,0,34,49" RenderTransformOrigin="0.5,0.5" Height="63" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="146">
        <Rectangle.RenderTransform>
            <ScaleTransform x:Name="transform" CenterX="0" CenterY="0" />
        </Rectangle.RenderTransform>
        <Rectangle.Effect>
            <DropShadowEffect BlurRadius="18"/>
        </Rectangle.Effect>
        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <!--Multiply the width by 2-->
                        <DoubleAnimation From="1" To="2" 
                                        Storyboard.TargetName="transform"
                                        Storyboard.TargetProperty="ScaleX"/>
                            <!--Multiply the height by 2-->
                            <DoubleAnimation From="1" To="2" 
                                        Storyboard.TargetName="transform"
                                        Storyboard.TargetProperty="ScaleY"/>
                            <!--Shifts the center X by 100-->
                            <DoubleAnimation From="0" To="100" 
                                        Storyboard.TargetName="transform"
                                        Storyboard.TargetProperty="CenterX"/>
                            <!--Shifts the center Y by 100-->
                            <DoubleAnimation From="0" To="100" 
                                        Storyboard.TargetName="transform"
                                        Storyboard.TargetProperty="CenterY"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Rectangle.Triggers>
    </Rectangle>
</Grid>

SkewTransform:

The SkewTransform is applied to the dimensions like the previous one but partially, it only transform a part of the given visual according to a rotation angle either in the X or the Y direction by leaving the other side intact.  

It has AngleX and AngleY properties. The both properties accept either positive or negative properties and they are used to rotate those partial part according to two senses, for the AngleX the positive sens is counter clockwise sense and the negative sense is the clockwise one.

<Canvas>
    <Rectangle Fill="Red" Canvas.Left="20"
           Canvas.Top="51"  Height="47" Name="rectangle1" 
           Stroke="Black" Width="132" >
    </Rectangle>
    <Rectangle Fill="Red" Canvas.Left="131"  Canvas.Top="144" 
       Height="47" Name="rectangle2" Stroke="Black" Width="132">
        <Rectangle.RenderTransform>
            <SkewTransform AngleX="45"/>
        </Rectangle.RenderTransform>
    </Rectangle>
    <Rectangle Fill="Red" Canvas.Left="237" Canvas.Top="243"
         Height="47" Name="rectangle3" Stroke="Black" Width="132">
        <Rectangle.RenderTransform>
            <SkewTransform AngleX="-45"/>
        </Rectangle.RenderTransform>
    </Rectangle>
    <TextBlock Canvas.Left="20" Canvas.Top="17" Height="21"
               Name="textBlock1" Width="176" Text="No skewing transformation" />
    <TextBlock Canvas.Left="42" Canvas.Top="115" Height="19"
                Name="textBlock2"
 Text="Skewing according to the X axis positively by 45 dgrees"  
  Width="298" />
    <TextBlock Canvas.Left="92" Canvas.Top="213" 
          Height="23" Name="textBlock3" Text="Skewing according to  
             the X axis negatively by 45 dgrees" Width="299" />
</Canvas>

4.gif

Meanwhile, for the AngleY the positive sense is clockwise sense and the negative sense is the counter clockwise one

<Canvas>
    <Rectangle Fill="Red" Canvas.Left="20"
                Canvas.Top="51" Height="47"
                Name="rectangle1" Stroke="Black" Width="132" >
    </Rectangle>
    <Rectangle Fill="Red" Canvas.Left="28"
               Canvas.Top="139"  Height="47"
               Name="rectangle2" Stroke="Black" Width="100">
        <Rectangle.RenderTransform>
            <SkewTransform AngleY="45"/>
        </Rectangle.RenderTransform>
    </Rectangle>
    <Rectangle Fill="Red" Canvas.Left="323"
               Canvas.Top="316" Height="47"
               Name="rectangle3" Stroke="Black" Width="100">
        <Rectangle.RenderTransform>
            <SkewTransform AngleY="-45"/>
        </Rectangle.RenderTransform>
    </Rectangle>
    <TextBlock Canvas.Left="20" Canvas.Top="17" Height="21"
                        Name="textBlock1" Width="176" Text="No
                         skewing transformation" />
    <TextBlock Canvas.Left="20" Canvas.Top="115" Height="19"  
               Name="textBlock2"  Text="Skewing according to the X axis positively by 45 dgrees"  Width="298" />
    <TextBlock Canvas.Left="241" Canvas.Top="192" 
      Height="23" Name="textBlock3"
      Text="Skewing according to the X axis negatively by 45 dgrees"
      Width="299" />
</Canvas>