
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1122 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
Rect rect = new Rect(50, 50, 105, 55); drawCtx.DrawRectangle(Brushes.AliceBlue, new Pen(Brushes.Blue, 5), rect);
}
//Register our visual with the object tree,
//to ensure it supports routed events, hit testing, etc.
AddVisualChild(rectVisual);
AddLogicalChild(rectVisual);
}
//Necessary overrides. The WPF graphics system
//will call these to figure out how many items to
//render and what to render.
protected override int VisualChildrenCount
{
get { return NumberOfVisualItems; }
}
protected override Visual GetVisualChild(int index)
{
// Collection is zero based, so subtract 1. if (index != (NumberOfVisualItems – 1))
throw new ArgumentOutOfRangeException("index", "Don't have that visual!"); return rectVisual;
}
}
Notice that the DrawingVisual type (rectVisual) provides the RenderOpen() method, which will return a DrawingContext object. Similar to GDI+’s Graphics object, DrawingContext has numerous methods that can be used to render a variety of items (DrawRectangle(), DrawEllipse(), etc.). Once you have constructed your rectangle, you make calls to two inherited methods (AddVisualChild() and AddLogicalChild()) which, while technically optional, ensure your custom Visual-derived type integrates into the window’s tree of objects.
Last but not least, you are required to override the virtual VisualChildrenCount read-only property and GetVisualChild() method. These members are called by the WPF graphics engine to determine exactly what to render (a single DrawingVisual in this example).
As you can see, as soon as you move into the realm of working with Visual-derived types, you are knee-deep in procedural code and therefore have a great deal of control and power (and the associated complexity that follows).
Building a Custom Visual Rendering Agent
Your current custom Visual rendering operation was set up in such a way that the window’s content (e.g., the <StackPanel>) was blown away and therefore not rendered, in favor of the hard-coded DrawingVisual. Just to dig a bit deeper into the Visual programming layer, what if you wished to use XAML descriptions for a majority of your window’s rendering and dip into the Visual layer for just a small portion of the overall UI?
One approach to do so is to define a custom class deriving from FrameworkElement and override the virtual OnRender() method. This method (which is in fact what the Shape-derived types use to render their output) can contain the same sort of code found in our previous CreateRectVisual() helper method. Once you have defined this custom class, you can then refer to your custom class type from within a window’s XAML description.


1124 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
Figure 30-4. Three rectangles, three approaches
Picking Your Poison
At this point you have seen three different approaches to interacting with the WPF 2D graphical rendering services (shapes, drawings, and visuals). By and large, the need to render graphics using the Visual-derived types is only necessary if you are building custom controls, or you need a great deal of control over the rendering process. This is a good thing, as working with Visual and friends entails a healthy amount of effort compared to simple XAML descriptions. Given this, I will not dive into the Visual rending APIs beyond this point in the chapter (do feel free to consult the .NET Framework 3.5 SDK documentation for further details if you are interested).
Using the Drawing-derived types is a perfect middle-of-the-road approach, as these types still support core non-UI services (such as hit testing, etc.) at a lower cost than the Shape types. While this approach does entail more markup than required by the Shape types, you end up with an application using less overhead. We will examine more details of the Drawing-derived types a bit later in this chapter.
That being said, however, the Shape types are still a perfectly valid approach when you need to render a handful of 2D images within a given window. Recall that if you truly do need 2D shapes that are just about as capable as traditional UI elements, the Shape types are a perfect choice, given that the required infrastructure is already in place.
■Note Always remember that your choice of rendering services can affect your application’s performance. Thankfully, we are provided with a collection of various WPF profiling utilities to monitor your current application. Look up the topic “Performance Profiling Tools for WPF” within the .NET Framework 3.5 SDK documentation for further details.
Exploring the Shape-Derived Types
To continue exploring 2D graphical rendering, let’s start by digging into the details of the members of the System.Windows.Shapes namespace. Recall these types provide the most straightforward, yet most bloated, way to render a two-dimensional image. This namespace (defined in the PresentationFramework.dll assembly) is quite small and consists of only six sealed types that extend the abstract Shape base class: Ellipse, Line, Path, Polygon, Polyline, and Rectangle.

CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1125 |
Because the Shape base class is-a FrameworkElement, you are able to assign derived types as content using XAML or procedural C# code without the additional complexities of working with drawing geometries:
<Window x:Class="SomeShapes.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300" Width="300" >
<!-- A window with a circle as content -->
<Ellipse Height = "100" Width = "100" Fill = "Black" /> </Window>
Like any other UI element, if you are building a window that requires multiple contained widgets, you will need to define your 2D types within a panel type, as described in Chapter 29.
The Functionality of the Shape Base Class
While most of Shape’s functionality comes from its long list of parent classes, this type does define some specific properties (most of which are dependency properties) that are common to the child types, some of the more interesting of which are shown in Table 30-1.
Table 30-1. Key Properties of the Shape Base Class
Properties |
Meaning in Life |
Fill |
Allows you to specify a brush type to render the interior part of a |
|
derived type. |
GeometryTransform |
Allows you to apply a transformation to the rendering of the derived |
|
type. |
Stretch |
Describes how to fill a shape within its allocated space. This is |
|
controlled using the corresponding System.Windows.Media.Stretch |
|
enumeration. |
Stroke, StrokeDashArray, These (and other) stroke-centric properties control how lines are
StrokeEndLineCap, |
configured when drawing the border of a shape. |
StrokeThickness |
|
Also recall that Shape-derived types have support for hit testing, themes and styles, tool tips, and numerous services.
Working with Rectangles, Ellipses, and Lines
To check out some of the derived types firsthand, create a new Visual Studio WPF Windows Application named FunWithSystemWindowsShapes. Declaring Rectangle, Ellipse, and Line types in XAML is quite straightforward and requires little comment. One interesting feature of the Rectangle type is that it defines RadiusX and RadiusY properties to allow you to render curved corners if you require.
Line represents its starting and end points using the X1, X2, Y1, and Y2 properties (given that “height” and “width” make little sense when describing a line). Without belaboring the point, consider the following <StackPanel>:
<StackPanel>
<!-- A line that monitors the mouse entering its area -->
<Line Name ="SimpleLine" X1 ="0" X2 ="50" Y1 ="0" Y2 ="50" Stroke ="DarkOliveGreen" StrokeThickness ="5"
ToolTip ="This is a line!" MouseEnter ="SimpleLine_MouseEnter"/>

1126 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
<!-- |
A rectangle with curved corners -- |
> |
<Rectangle RadiusX ="20" RadiusY ="50"
Fill ="DarkBlue" Width ="150" Height ="50"/>
</StackPanel>
The MouseEnter event of the SimpleLine object simply updates the Title property of the window with the location of the mouse cursor at the time it entered the Line object:
protected void SimpleLine_MouseEnter(object sender, MouseEventArgs args)
{
this.Title = String.Format("Mouse entered at: {0}", args.GetPosition(SimpleLine));
}
Working with Polylines, Polygons, and Paths
The Polyline type allows you define a collection of (x, y) coordinates (via the Points property) to draw a series of connected line segments that do not require connecting ends. The Polygon type is similar; however, it is programmed in such a way that it will always close the starting and ending points. Consider the following additions to the current <StackPanel>:
<!-- |
Polyline types do not have connecting ends -- |
> |
<Polyline Stroke ="Red" StrokeThickness ="20" StrokeLineJoin ="Round" Points ="10,10 40,40 10,90 300,50"/>
<!-- A Polygon always closes the end points-->
<Polygon Fill ="AliceBlue" StrokeThickness ="5" Stroke ="Green" Points ="40,10 70,80 10,50" />
Figure 30-5 shows the rendered output for each of these Shape-derived types.
Figure 30-5. Rendered Shape-derived types
The final type, Path (not examined here), can be considered the superset of Rectangle, Ellipse, Polyline, and Polygon in that Path can render any of these types. In fact, all 2D types could be rendered using nothing but Path (however, doing so would require additional work).

CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1127 |
■Source Code The FunWithSystemWindowsShapes project can be found under the Chapter 30 subdirectory.
Working with WPF Brushes
Each of the WPF graphical rendering options (shape types, drawing types, and visual types) makes extensive use of brushes, which allow you to control how the interior of a 2D surface is filled. WPF provides six different brush types, all of which extend System.Windows.Media.Brush. While Brush is abstract, the descendents described in Table 30-2 can be used to fill a region with just about any conceivable option.
Table 30-2. WPF Brush-Derived Types
Brush Type |
Meaning in Life |
DrawingBrush |
Paints an area with a Drawing-derived object (GeometryDrawing, |
|
ImageDrawing, or VideoDrawing) |
ImageBrush |
Paints an area with an image (represented by an ImageSource object) |
LinearGradientBrush |
A brush used to paint an area with a linear gradient |
RadialGradientBrush |
A brush used to paint an area with a radial gradient |
SolidColorBrush |
A brush consisting of a single color, set with the Color property |
VisualBrush |
Paints an area with a Visual-derived object (DrawingVisual, |
|
Viewport3DVisual, and ContainerVisual) |
|
|
The DrawingBrush and VisualBrush types allow you to build a brush based on the Drawing- or Visual-derived types examined at the beginning of this chapter. The remaining brush types are quite straightforward to make use of and are very close in functionality to similar types found within GDI+. The following sections present a quick overview of SolidColorBrush,
LinearGradientBrush, RadialGradientBrush, and ImageBrush.
■Note Given that these examples will not respond to any events, you can enter each of the following examples directly into the custom XAML viewer you created in Chapter 28, rather than creating new Visual Studio 2008 WPF Application project workspaces.
Building Brushes with Solid Colors
The SolidColorBrush type provides the Color property to establish a solid colored brush type. The Color property takes a System.Windows.Media.Color type, which contains various properties (such as A, R, G, and B) to establish the color itself. While the capability to have solid colors is useful, ironically you typically will not need to directly create a SolidColorBrush explicitly, given that XAML supports a type converter that maps known color names (e.g., "Blue") to a SolidColorBrush object behind the scenes. Consider the following approaches to fill an Ellipse with a solid color:
<StackPanel>
<!-- Solid brush using type converter -->
<Ellipse Fill ="DarkRed" Height ="50" Width ="50"/>

1128 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
<!-- |
Using the SolidColorBrush type -- |
> |
<Ellipse Height ="50" Width ="50"> <Ellipse.Fill>
<SolidColorBrush Color ="DarkGoldenrod"/> </Ellipse.Fill>
</Ellipse>
<!-- |
Using the SolidColorBrush and Color type -- |
> |
<Ellipse Height ="50" Width ="50"> <Ellipse.Fill>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color A ="40" R ="100" G ="87" B ="98"/> </SolidColorBrush.Color>
</SolidColorBrush>
</Ellipse.Fill>
</Ellipse>
</StackPanel>
The output is what you would expect (three circles of various solid colors); however, the approach you take to define the color scheme will be based on the level of flexibility you require. For example, if you do need to change the value of the Opacity property (to control transparency), you will need to declare a <SolidColorBrush> element to gain direct access to its members. In all other cases, you are able to make use of a simple string value assigned to the Fill property.
■Note The WPF graphics API provides a helper class named Brushes, which defines properties for dozens of predefined colors. This is very useful when you need a solid colored brush in procedural code.
Working with Gradient Brushes
The two gradient brush types (LinearGradientBrush and RadialGradientBrush) allow you to fill an area by transitioning between two (or more) colors. The distinction is that while a LinearGradientBrush always transitions between colors using a straight line (which could, of
course, be rotated into any position using a graphical transformation or by setting the starting and ending point), a RadialGradientBrush transitions from a specified starting point outward within an elliptical boundary. Consider the following:
<!-- |
A rectangle with a linear fill -- |
> |
<Rectangle RadiusX ="15" RadiusY ="15" Height ="40" Width ="100"> <Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5"> <GradientStop Color="LimeGreen" Offset="0.0" /> <GradientStop Color="Orange" Offset="0.25" /> <GradientStop Color="Yellow" Offset="0.75" /> <GradientStop Color="Blue" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- |
An ellipse with a radial fill -- |
> |
<Ellipse Height ="75" Width ="75"> <Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.5" Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">


1130 CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES
Working with WPF Pens
In comparison to brushes, the topic of pens is trivial, as the Pen type is really nothing more than a Brush in disguise. Specifically, Pen represents a brush type that has a specified thickness, represented by a double value. Given this point, you could create a Pen that has a Thickness property value so large that it appears to be, in fact, a brush! However, in most cases you will build a Pen of more modest thickness to represent how to render the outline of a 2D image.
In many cases, you will not directly need to create a Pen type, as this will be done indirectly when you assign a value to properties such as StrokeThickness. However, building a custom Pen type is very handy when working with Drawing-derived types (described next). Before we see a customized pen doing something useful, consider the following example:
<Pen Thickness="10" LineJoin="Round" EndLineCap="Triangle" StartLineCap="Round" />
This particular Pen type has set the LineJoin property, which controls how to render the connection point between two lines (e.g., the corners). EndLineCap, as the name suggests, controls how to render the endpoint of a line stroke (a triangle in this case), while StartLineCap controls the same setting at the line’s point of origin.
A Pen can also be configured to make use of a dash style, which affects how the pen draws the line itself. The default setting is to use a solid color (as dictated by a given brush); however, the DashStyle property may be assigned to any custom DashStyle object. While creating a custom DashStyle object gives you complete control over how a Pen should render its data, the DashStyles helper class defines a number of static members that provide common default styles. Because these are static members of a class rather than values from an enumeration, we must make use of the XAML {x:Static} markup extension:
<Pen Thickness="10" LineJoin="Round" EndLineCap="Triangle"
StartLineCap="Round" DashStyle = "{x:Static DashStyles.DashDotDot}" />
Now that you have a better idea of the Pen type, let’s make use of some of them within various Drawing-derived types.
Exploring the Drawing-Derived Types
Recall that while the Shape types allow you to generate any sort of interactive two-dimensional surface, they entail quite a bit of overhead due to their rich inheritance chain. As an alternative, WPF provides a sophisticated drawing and geometry programming interface, which renders more lightweight 2D images. The entry point into this API is the abstract System.Windows.Media.Drawing class, which on its own does little more than define a bounding rectangle to hold the rendering. WPF provides five types that extend Drawing, each of which represents a particular flavor of drawing content, as described in Table 30-3.
Table 30-3. WPF Drawing-Derived Types
Type |
Meaning in Life |
DrawingGroup |
Used to combine a collection of separate Drawing-derived types into a single |
|
composite rendering. |
GeometryDrawing |
Used to render 2D shapes. |
GlyphRunDrawing |
Used to render textual data using WPF graphical rendering services. |
ImageDrawing |
Used to render an image file into a bounding rectangle. |
VideoDrawing |
Used to play (not “draw”) an audio file or video file. This type can only be fully |
|
exploited using procedural code. If you wish to play videos via XAML, the |
|
MediaPlayer type is a better choice. |
|
|

CHAPTER 30 ■ WPF 2D GRAPHICAL RENDERING, RESOURCES, AND THEMES |
1131 |
While each of these types is useful in its own right, GeometryDrawing is the type of interest when you wish to render 2D images, and it is the one we will focus on during this section. In a nutshell, the GeometryDrawing type represents a geometric type detailing the structure of the 2D image, a Brush-derived type to fill its interior, and a Pen to draw its border.
The Role of Geometry Types
The geometric structure described by a GeometryDrawing type is actually one of the WPF geometrycentric class types, or possibly a collection of geometry-centric types that work together as a single unit. Each of these geometries can be expressed in XAML or via C# code. All of the geometries derive from the System.Windows.Media.Geometry base class, which defines a number of useful members common to all derived types, some of which appear in Table 30-4.
Table 30-4. Select Members of the System.Windows.Media.Geometry Type
Member |
Meaning in Life |
Bounds |
A property used to establish the current bounding rectangle. |
FillContains() |
Allows you to determine if a given Point (or other Geometry type) is within |
|
the bounds of a particular Geometry-derived type. Obviously, this is useful |
|
for hit-testing calculations. |
GetArea() |
Returns a double that represents the entire area a Geometry-derived type |
|
occupies. |
GetRenderBounds() |
Returns a Rect that contains the smallest possible rectangle that could be |
|
used to render the Geometry-derived type. |
Transform |
Allows you to assign a Transform object to the geometry to alter the |
|
rendering. |
|
|
WPF provides a good number of Geometry-derived types out of the box, and these can be grouped into two simple categories: basic shapes and paths. The first batch of geometric types,
RectangleGeometry, EllipseGeometry, LineGeometry, and PathGeometry, are used to render basic shapes. As luck would have it, these four types mimic the functionality of the System.Windows. Media.Shapes types you have already examined (and in many cases they have identical members).
For many of your rendering operations, the basic shape types will do just fine. Do be aware, however, that if you require more exotic geometries, WPF supplies numerous auxiliary types that work in conjunction with the PathGeometry type. In a nutshell, PathGeometry maintains a collection of “path segments,” which can be any of the following: ArchSegment, BezierSegment, LineSegment,
PolyBezierSegment, PolyLineSegment, PolyQuadraticBezierSegment, and QuadraticBezierSegment.
Dissecting a Simple Drawing Geometry
Let’s take a closer look at the <GeometryDrawing> type created at the beginning of this chapter:
<GeometryDrawing Brush ="LightBlue"> <GeometryDrawing.Pen>
<Pen Brush ="Blue" Thickness ="5"/> </GeometryDrawing.Pen> <GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,50"/> </GeometryDrawing.Geometry>
</GeometryDrawing>