
Pro CSharp 2008 And The .NET 3.5 Platform [eng]-1
.pdf
1132 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
Recall that a <GeometryDrawing> consists of a brush, pen, and any of the WPF geometry types. Here, we have indirectly defined a light blue SolidColorBrush using the Brush property in the open element. The Pen type is declared using property-element syntax, to define a blue brush with a specific thickness. Here, we are making use of a <RectangleGeometry> as the value assigned to the
Geometry property of the <GeometryDrawing> type.
If you attempt to author this XAML directly within a <Page> scope using xamlpad.exe (or within any ContentControl-derived type), you will generate a markup error that essentially tells you that <GeometryDrawing> does not extend the UIElement base class and therefore cannot be used as a value to the Content property.
This brings up an interesting aspect of working with the Drawing-derived types: they do not have any user interface in and of themselves! Types such as <GeometryDrawing> simply describe how a 2D element would look if placed into a suitable container. WPF provides three different hosting objects for Drawing objects: DrawingImage, DrawingBrush, and DrawingVisual.
Containing Drawing Types in a DrawingImage
The DrawingImage type allows you to plug your drawing geometry into a WPF <Image> control. Thus, if you wish to render the previous <GeometryDrawing>, you would need to wrap it as so:
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing> <GeometryDrawing Brush ="LightBlue">
...
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
Notice how the Source property of the Image type is assigned a Drawing-derived type.
Containing Drawing Types in a DrawingBrush
If you instead wrap a <GeometryDrawing> using a DrawingBrush type, you have essentially created a complex custom brush, given that DrawingBrush is actually one of the Brush-derived types (more information on brushes in the next section). You could then use it anywhere a Brush type is required, such as the Background property of the <Window> type:
<Window x:Class="FunWithDrawingAndGeometries.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="FunWithDrawingAndGeometries" Height="190" Width="224">
<!-- Set the background of this window to a custom DrawingBrush -->
<Window.Background>
<DrawingBrush>
<DrawingBrush.Drawing> <GeometryDrawing Brush ="LightBlue">
<GeometryDrawing.Pen>
<Pen Brush ="Blue" Thickness ="5"/> </GeometryDrawing.Pen>

CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1133 |
<GeometryDrawing.Geometry> <RectangleGeometry Rect="0,0,100,50"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Background>
</Window>
A More Complex Drawing Geometry
A DrawingImage object can be composed of multiple individual Drawing objects, placed in a <DrawingGroup> in order to build much more elaborate 2D images. Consider the following Image type, which uses a <DrawingImage> as its source:
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<!-- A group of various geometries -->
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,20,20" /> <RectangleGeometry Rect="160,120,20,20" />
<EllipseGeometry Center="75,75" RadiusX="50" RadiusY="50" /> <LineGeometry StartPoint="75,75" EndPoint="180,0" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<!-- A custom pen to draw the borders -->
<GeometryDrawing.Pen>
<Pen Thickness="10" LineJoin="Round" EndLineCap="Triangle" StartLineCap="Round">
<Pen.Brush>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="Red" /> <GradientStop Offset="1.0" Color="Green" />
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
The <DrawingImage> is composed of a <DrawingGroup> that contains a <GeometryGroup> to build an image consisting of two rectangles, an ellipse, and a line. The borders of our images are rendered using a custom pen type, which is in turn composed of a custom LinearGradientBrush. The end result can be seen in Figure 30-7.


CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1135 |
The Role of UI Transformations
Before moving on to the topic of animation services, allow me to wrap up our look at 2D graphic rendering by examining the topic of transformations. WPF ships with numerous types that extend the Transform abstract base class, which can be applied to any FrameworkElement (e.g., descendents of Shape as well as UI elements such as Buttons, TextBoxes, etc.). Using these types, you are able to render a FrameworkElement at a given angle, skew the image across a surface, and expand or shrink the image in a variety of ways.
Transform-Derived Types
Table 30-5 documents many of the key out-of-the-box Transform types.
Table 30-5. Key Descendents of the System.Windows.Media.Transform Type
Type |
Meaning in Life |
MatrixTransform |
Creates an arbitrary matrix transformation that is used to manipulate |
|
objects or coordinate systems in a 2D plane |
RotateTransform |
Rotates an object clockwise about a specified point in a 2D (x, y) coordinate |
|
system |
ScaleTransform |
Scales an object in the 2D (x, y) coordinate system |
SkewTransform |
Skews an object in the 2D (x, y) coordinate system |
TransformGroup |
Represents a composite Transform composed of other Transform objects |
|
|
Once you create a Transform-derived object, you can apply it to two properties provided by the FrameworkElement base class. The LayoutTransform property is helpful in that the transformation occurs before elements are rendered into a panel. The RenderTransform property, on the other hand, occurs after the items are in their container, and therefore it is quite possible that elements can be transformed in such a way that they overlap each other. As well, types that extend UIElement can also assign a value to the RenderTransformOrigin property, which simply specifies an (x, y) position to begin the transformation.
Applying Transformations
Assume we have a <Grid> containing a single row with four columns. Within each cell, we will rotate, skew, and scale various UIElements, for example:
<!-- A Rectangle with a rotate transformation -->
<Rectangle Height ="100" Width ="40" Fill ="Red" Grid.Row="0" Grid.Column="0"> <Rectangle.LayoutTransform>
<RotateTransform Angle ="45"/> </Rectangle.LayoutTransform>
</Rectangle>
<!-- A Button with a skew transformation -->
<Button Content ="Click Me!" Grid.Row="0" Grid.Column="1" Width="95" Height="40"> <Button.RenderTransform>
<SkewTransform AngleX ="20" AngleY ="20"/> </Button.RenderTransform>
</Button>

1136 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
<!-- An Ellipse that has been scaled by 20% -->
<Ellipse Fill ="Blue" Grid.Row="0" Grid.Column="2" Width="5" Height="5"> <Ellipse.RenderTransform>
<ScaleTransform ScaleX ="20" ScaleY ="20"/> </Ellipse.RenderTransform>
</Ellipse>
<!-- A Button that has been skewed, rotated, and skewed again -->
<Button Content ="Me Too!" Grid.Row="0" Grid.Column="3" Width="50" Height="40"> <Button.RenderTransform>
<TransformGroup>
<SkewTransform AngleX ="20" AngleY ="20"/> <RotateTransform Angle ="45"/> <SkewTransform AngleX ="5" AngleY ="20"/> </TransformGroup>
</Button.RenderTransform>
</Button>
Our first type, the <Rectangle>, makes use of the RotateTransform type that renders the UI item at a 45-degree angle via the Angle property. The first <Button> type uses a SkewTransform object, which slants the rendering of the widget based on (at minimum) the AngleX and AngleY properties. The <ScaleTransform> type used by the <Ellipse> grows the height and width of the circle quite a bit. Notice, for example, that the Height and Width properties of the <Ellipse> are set to 5, while the rendered output is much larger. Last but not least, the final <Button> type makes use of the <TransformGroup> type to apply a skew and a rotation. Figure 30-9 shows the rendered output.
Figure 30-9. Applying transformations
■Source Code The FunWithTransformations.xaml file can be found under the Chapter 30 subdirectory.
Understanding WPF’s Animation Services
In addition to providing a full-fledged API to support 2D (and 3D) graphical rendering, WPF supplies a programming interface to support animation services. The term “animation” may bring to mind visions of spinning company logos, a sequence of rotating image resources (to provide the illusion of movement), text bouncing across the screen, or specific types of programs such as video games or multimedia applications.
While WPF’s animation APIs could certainly be used for such purposes, animation can be used anytime you wish to give an application additional flair. For example, you could build an animation

CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1137 |
for a button on a screen that magnifies slightly in size when the mouse cursor hovers within its boundaries (and shrinks back once the mouse cursor moves beyond the boundaries). Perhaps you wish to animate a window so that it closes using a particular visual appearance (such as slowly fading into transparency). The short answer is that WPF’s animation support can be used within any sort of application (business application, multimedia programs/video games, etc.) whenever you wish to provide a more engaging user experience.
Like many other aspects of WPF, the notion of building animations is nothing new in and of itself. However, unlike other APIs you may have used in the past (including GDI+), developers are not required to author the necessary infrastructure by hand. Under WPF, we have no need to create the background threads (or timers) used to advance the animation sequence, define custom types to represent the animation, or bother with numerous mathematical calculations.
Like other aspects of WPF, we are able to build an animation entirely using XAML, entirely using C# code, or a combination of the two. Furthermore, Microsoft Expression Blend (mentioned a few times within these WPF-centric chapters) can be used to design an animation using integrated tools and wizards without seeing a bit of C# or XAML in the foreground. This approach is ideal for graphic artists, who may not feel comfortable viewing such details.
The Role of Animation-Suffixed Types
To understand WPF’s animation support, we must begin by examining the core animation types within the System.Windows.Media.Animation namespace of PresentationCore.dll. Here you will find a number of class types that all take the Animation suffix (ByteAnimation, ColorAnimation, DoubleAnimation, In32Animation, etc.). Obviously, these types are not used to somehow provide an animation sequence directly to a variable to a particular data type (after all, how exactly could we animate the value “9” using an Int32Animation?). Rather, these Animation-suffixed types can be connected to any dependency property of a given type that matches the underlying types.
■Note Allow me to repeat this key point: Animation-suffixed types can only work in conjunction with dependency properties, not normal CLR properties (see Chapter 29). If you attempt to apply animation objects to CLR properties, you will receive a compile-time error.
For example, consider the Label type’s Height and Width properties, both of which are dependency properties wrapping a double. If you wish to define an animation that would increase the height of a label over a time span, you could connect a DoubleAnimation object to the Height property and allow WPF to take care of the details of performing the actual animation itself. By way of another example, if you wish to transition the color of a brush type from green to yellow, you could do so using the ColorAnimation type.
Regardless of which Animation-suffixed type you wish to make use of, they all define a handful of key properties that control the starting and ending values used to perform the animation:
•To: This property represents the animation’s ending value.
•From: This property represents the animation’s starting value.
•By: This property represents the total amount by which the animation changes its starting value.
Despite the fact that all Animation-suffixed types support the To, From, and By properties, they do not receive them via virtual members of a base class. The reason for this is that the underlying types wrapped by these properties vary greatly (integers, colors, Thickness objects, etc.), and representing all possibilities using a System.Object would cause numerous boxing/unboxing penalties for stack-based data.


CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1139 |
Now, within each Click event handler, author the following code:
public partial class MainWindow : System.Windows.Window
{
public MainWindow()
{
InitializeComponent();
}
protected void btnAnimatelblMessage_Click(object sender, RoutedEventArgs args)
{
// This will grow the height of the label.
DoubleAnimation dblAnim = new DoubleAnimation(); dblAnim.From = 40;
dblAnim.To = 60; lblHeight.BeginAnimation(Label.HeightProperty, dblAnim);
}
protected void btnAnimatelblTransparency_Click(object sender, RoutedEventArgs args)
{
// This will change the opacity of the label.
DoubleAnimation dblAnim = new DoubleAnimation(); dblAnim.From = 1.0;
dblAnim.To = 0.0; lblTransparency.BeginAnimation(Label.OpacityProperty, dblAnim);
}
}
Notice in each Click event handler we set the From and To values of the DoubleAnimation type to represent the beginning and ending value. After this point, we call BeginAnimation() on the correct Label object, passing in the correct dependency property field of the related widget (again, the Label) followed by the Animation-suffixed object used to perform the animation.
■Note Recall from our examination of dependency properties in Chapter 29 that public read-only static fields of type DependencyObject are used to represent a given dependency property exposed by the (optional) CLR property wrapper.
If you now run your application and click each button, you will find that the lblHeight label will grow in size, while the lblTransparency button will slowly fade from view.
Controlling the Pacing of an Animation
By default, an animation will take approximately one second to transition between the values assigned to the From and To properties. For example, if you were to modify the Click event handler that grows the Height of the Label from 40 to 200 (a larger increase than what we currently have in place), it would still take approximately one second to do so.
If you wish to define a custom amount of time for an animation’s transition, you may do so via the Duration property, which can be set to an instance of a Duration object. Typically, the time span is established by passing a TimeSpan object to the Duration’s constructor. Consider the following update to the current Click handlers, which will grow the label’s height over four seconds and fade the other label from view over the course of ten seconds:

1140 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
protected void btnAnimatelblMessage_Click(object sender, RoutedEventArgs args)
{
// Take 4 seconds to complete the animation.
DoubleAnimation dblAnim = new DoubleAnimation(); dblAnim.From = 40;
dblAnim.To = 200;
dblAnim.Duration = new Duration(TimeSpan.FromSeconds(4)); lblHeight.BeginAnimation(Label.HeightProperty, dblAnim);
}
protected void btnAnimatelblTransparency_Click(object sender, RoutedEventArgs args)
{
// This will change the opacity of the label
DoubleAnimation dblAnim = new DoubleAnimation(); dblAnim.From = 1.0;
dblAnim.To = 0.0;
dblAnim.Duration = new Duration(TimeSpan.FromSeconds(10)); lblTransparency.BeginAnimation(Label.OpacityProperty, dblAnim);
}
■Note The BeginTime property of an Animation-suffixed type also takes a TimeSpan object. Recall this property can be set to establish a wait time before starting an animation sequence.
Reversing and Looping an Animation
You can also tell Animation-suffixed types to play an animation in reverse at the completion of the animation sequence by setting the AutoReverse property to true. For example, the following update to our Click event handler will cause the Label to grow from 40 to 100 pixels in height, after which it will “shrink” from 100 back to 40 (over the course of eight seconds, four seconds each “direction”):
// Reverse when done. dblAnim.AutoReverse = true;
If you wish to have an animation repeat some number of times (or to never stop once activated), you can do so using the RepeatBehavior property, which is common to all Animation-suffixed types. The RepeatBehavior property is set to an object of the same name. If you pass in a simple numerical value to the constructor, you can specify a hard-coded number of times to repeat. On the other hand, if you pass in a TimeSpan object to the constructor, you can establish an amount of time the animation should repeat. Finally, if you wish an animation to loop ad infinitum, you can simply specify RepeatBehavior.Forever. Consider the following ways we could change the height of our Label object:
// Loop forever.
dblAnim.RepeatBehavior = RepeatBehavior.Forever;
// Loop three times.
dblAnim.RepeatBehavior = new RepeatBehavior(3);
// Loop for 30 seconds.
dblAnim.RepeatBehavior = new RepeatBehavior(TimeSpan.FromSeconds(30));
