Windows Presentation Foundation

Hello, WPF

WPF from Scratch
Navigation Applications
Content Model
Layout
Controls
Data Binding
Dependency Properties
Resources
Styles and Control Templates
Graphics
Application Deployment
Where Are We?

Layout

Introduction
Layout Basics
DockPanel
StackPanel
Grid
Canvas
Viewbox
Text Layout
Common Layout Properties
When Content Doesn't Fit
Custom Layout
Where Are We?

Controls

Introduction
What Are Controls?
Handling Input
Built-In Controls
Where Are We?

Data Binding

Introduction
Without Data Binding
Data Binding
Binding to List Data
Data Sources
Master-Detail Binding
Where Are We?

Styles and Control Templates

Introduction
Without Styles
Inline Styles
Named Styles
Element-Typed Styles
Data Templates and Styles
Triggers
Control Templates
Where Are We?

Resources

Introduction
Creating and Using Resources
Resources and Styles
Binary Resources
Global Applications
Where Are We?

Graphics

Introduction
Graphics Fundamentals
Shapes
Brushes and Pens
Transformations
Visual-Layer Programming
Video and 3-D
Where Are We?

Animation

Animation Fundamentals
Timelines
Storyboards
Key Frame Animations
Creating Animations Procedurally
Where Are We?

Custom Controls

Introduction
Custom Control Basics
Choosing a Base Class
Custom Functionality
Templates
Default Visuals
Where Are We?

ClickOnce Deployment

A Brief History of Windows Deployment
ClickOnce: Local Install
The Pieces of ClickOnce
Publish Properties
Deploying Updates
ClickOnce: Express Applications
Choosing Local Install versus Express
Signing ClickOnce Applications
Programming for ClickOnce
Security Considerations
Where Are We?

Shapes

7.2. Shapes

Shapes are drawing primitives, represented as elements in the user interface tree. WPF supports a variety of different shapes and provides element types for each of them.

7.2.1. Base Shape Class

All of the elements listed in this section derive from a common abstract base class, Shape. Although you cannot use this type directly, it is useful to know about it, because it defines a common set of features that you can use on all shapes. These common properties are all concerned with the way in which the interior and outline of the shape are painted.

The Fill property specifies the Brush that will be used to paint the interior. The Line and Polyline classes don't have interiors, so they will ignore this property. (This was simpler than complicating the inheritance hierarchy by having separate Shape and FilledShape base classes.) The Stroke property specifies the Brush that will be used to paint the outline of the shape.

If you do not specify either a Fill or a Stroke for your shape, it will be invisible, because both of these properties are transparent by default.


It may seem peculiar that the Stroke property is of type Brush. As we saw earlier, WPF defines a Pen class for specifying a line's thickness, dash patterns, and the like, so it would make more sense if the Stroke property were of type Pen. WPF does in fact use a Pen internally to draw the outline of a shape. The Stroke property is of type Brush mainly for convenienceall of the Pen features are exposed through separate properties on Shape, as shown in Table 7-1. This simplifies the markup in scenarios where you're happy to use the default pen settingsyou don't need to provide a full Pen definition just to set the outline color.

Table 7-1. Shape Stroke properties and Pen equivalents

Shape property

Pen equivalent

Stroke

Brush

StrokeThickness

Thickness

StrokeLineJoin

LineJoin

StrokeMiterLimit

MiterLimit

StrokeDashArray

DashArray

StrokeDashCap

DashCap

StrokeDashOffset

DashOffset

StrokeStartLineCap

StartLineCap

StrokeEndLineCap

EndLineCap


Brushes and pens are described in detail in the "Brushes and Pens" section, later in this chapter.

7.2.2. Rectangle

Rectangle does what its name suggests. As with any shape, it can be drawn either filled in, as an outline, or both. As well as drawing a normal rectangle, it can also draw one with rounded corners.

Rectangle doesn't provide any properties for setting its size and location. It relies on the same layout mechanism as any other UI element. The location is determined by the containing panel. The width and height can either be set automatically by the parent, or they can be set explicitly using the standard layout properties, Width and Height.

Example 7-6 shows a Rectangle on a Canvas panel. Here, the Width and Height have been set explicitly, and the location has been specified using the attached Canvas.Left and Canvas.Top properties.

Example 7-6. Rectangle with explicit size and position
<Canvas>
    <Rectangle Fill="Yellow" Stroke="Black"
               Canvas.Left="30" Canvas.Top="10"
               Width="100" Height="20" />
</Canvas>

Example 7-7 shows the other approach. None of the rectangles have had their location or size set explicitly. They rely on the containing Grid, instead. Figure 7-10 shows the result.

Example 7-7. Rectangles with size and position controlled by parent
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <Rectangle Grid.Column="0" Grid.Row="0" Fill="LightGray" />

    <Rectangle Grid.Column="1" Grid.Row="0" Fill="Black" />
    <Rectangle Grid.Column="0" Grid.Row="1" Fill="DarkGray" />
    <Rectangle Grid.Column="1" Grid.Row="1" Fill="White" />
</Grid>

Figure 7-10. Rectangles arranged by a Grid


A Rectangle will usually be aligned with the coordinate system of its parent panel. This means that its edges will usually be horizontal and vertical, although if the parent panel has been rotated, the Rectangle will of course be rotated along with it. If you want to rotate a Rectangle relative to its containing panel, you can use the RenderTransform property available on all user-interface elements, as demonstrated in Example 7-8, which illustrates the use of RenderTransform to rotate a series of rectangles. The result is shown in Figure 7-11.

Example 7-8. Rotating rectangles
<Canvas>
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Indigo" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Violet" RenderTransform="rotate 45" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Blue" RenderTransform="rotate 90" />

    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Cyan" RenderTransform="rotate 135" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Green" RenderTransform="rotate 180" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Yellow" RenderTransform="rotate 225" />

    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Orange" RenderTransform="rotate 270" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
      Fill="Red" RenderTransform="rotate 315" />
  </Canvas>

Figure 7-11. Rotated rectangles (Color Plate 21)


To draw a rectangle with rounded corners, use the RadiusX and RadiusY properties, as shown in Example 7-9. Figure 7-12 shows the result.

Example 7-9. Rounded rectangle
<Rectangle Width="100" Height="50" Fill="Black" RadiusX="30" RadiusY="40" />

Figure 7-12. Rectangle with rounded corners


7.2.3. Ellipse

Ellipse is similar to Rectangle. Obviously, it draws an ellipse rather than a rectangle, but the size, location, rotation, fill, and stroke of an Ellipse are controlled in exactly the same way as for a Rectangle, as Example 7-10 shows. The result is shown in Figure 7-13.

Example 7-10. Ellipse
<Ellipse Width="100" Height="50" Fill="Yellow" Stroke="Black" />

Figure 7-13. Ellipse


7.2.4. Line

The Line element draws a straight line from one point to another. It has four properties controlling the location of the start and end points: X1, Y1, X2, and Y2. These coordinates are relative to wherever the parent panel chooses to locate the Line. Consider Example 7-11.

Example 7-11. Two Lines in a StackPanel
<StackPanel Orientation="Vertical">
    <TextBlock Background="LightGray">Foo</TextBlock>
    <Line Stroke="Green" X1="20" Y1="10" X2="100" Y2="40" />
    <TextBlock Background="LightGray">Bar</TextBlock>

    <Line Stroke="Green" X1="0" Y1="10" X2="100" Y2="0" />
</StackPanel>

Example 7-11 uses a vertical StackPanel to arrange an alternating sequence of TextBlock and Line elements. The TextBlock elements have gray backgrounds to make it easier to see the vertical extent of each element. The results are shown in Figure 7-14.

Figure 7-14. Two Lines in a StackPanel


As you can see from Figure 7-14, the Line elements have been placed in the stack just like any other element. The StackPanel has allocated enough height to hold the line. The first of the lines is interesting in that there is some space between the bottom of the TextBlock above it and the start of the line. This is because the line's Y1 property has been set to 10, indicating that the line should start slightly below the top of the location allocated for Line element. The second Line element goes all the way to the top because its Y2 property is set to 0, again illustrating that the coordinate system of the line endpoints is relative to the area allocated to the Line by the containing panel.

There is no way to set the line endpoints automatically as part of your layout. (The only reason you can rely on the layout system to position Ellipse and Rectangle elements is because their basic dimensions are determined by rectangles and the layout system essentially deals with rectangular arrangement.) For example, you cannot tell the Line to be exactly as wide as the space allocated by the parent panel. If you want to do this, just use the Rectangle insteadyou can draw a line by creating a thin rectangle, rotating it if necessary. Or you could use a DrawingBrush or VisualBrush, as these can automatically scale drawings to fill the space available.

7.2.5. Polyline

A Polyline lets you draw a connected series of line segments. Instead of having properties for start and end points, Polyline has a Points property, containing a list of coordinate pairs, as shown in Example 7-12. WPF simply draws a line that goes through each of the points in turn, as shown in Figure 7-15.

Example 7-12. Polyline
<Polyline Stroke="Blue"
   Points="0,30 10,30 15,0 18,60 23,30 35,30 40,0 43,60 48,30 160,30" />

Figure 7-15. A Polyline


As with the Line class, the point coordinates in a Polyline are relative to wherever the containing panel chooses to locate the Polyline.

7.2.6. Polygon

Polygon is very similar to Polyline. It has a Points property that works in exactly the same way as Polyline's. The only difference is that while Polyline always draws an open shape, Polygon always draws a closed shape. To illustrate the difference, Example 7-13 contains a Polyline and a Polygon. All the same properties are set for both.

Example 7-13. A Polyline and a Polygon
<StackPanel Orientation="Horizontal">
  <Polyline Fill="Yellow" Stroke="Blue" Points="40,10 70,50 10,50"  />
  <Polygon  Fill="Yellow" Stroke="Blue" Points="40,10 70,50 10,50"  />
</StackPanel>

As you can see in Figure 7-16, the Polyline has ignored the Fill property. The shape does not have an interiorit is left open. The Polygon, on the other hand, has closed the shape by drawing an extra line segment between the last and first points, and it has painted the interior of the shape.

Figure 7-16. A Polyline and a Polygon


Because we are free to add points wherever we like to a Polygon, it is easy to end up with a self-intersecting shape, one whose edge crosses itself. With such shapes, there can be ambiguity about what counts as the interior of the shape. Figure 7-17 shows such a shape and two possible ways of filling it.

Figure 7-17. Two filling styles


The Polygon class provides a FillRule property to choose the way in which such potentially ambiguous regions are dealt with. (In some graphics systems, this is described as the winding rule.) WPF supports two fill rules. Example 7-14 is the markup for Figure 7-17 and shows both fill rules in use.

Example 7-14. Fill rules
<StackPanel Orientation="Horizontal">
  <Polygon Fill="Yellow" Stroke="Blue" FillRule="EvenOdd"
           Points="50,30 13,41 36,11 36,49 14,18"  />
  <Polygon Fill="Yellow" Stroke="Blue" FillRule="Nonzero"

           Points="50,30 13,41 36,11 36,49 14,18"  />
</StackPanel>

The default rule is EvenOdd, and this is used for the Polygon on the left of Figure 7-17. This is the simplest rule to understand. To determine whether a particular enclosed region is inside or outside the shape, the EvenOdd rule counts the number of lines you have to cross to get from that point to one completely outside of the shape. If this number is odd, the point is inside the shape. If it is even, the point is outside the shape.

The second fill rule, Nonzero, is more subtle. From Figure 7-17, you might have thought that any enclosed area was deemed to be inside the shape, but it's not quite that simple. The Nonzero rule performs a similar process to EvenOdd, but rather than simply counting the number of lines, it takes into account the direction in which the line is running. It either increments or decrements the count for each line it crosses, depending on the direction. If the sum total at the end is nonzero, the point is considered to be inside the shape.

In the Polygon on the right in Figure 7-17, the Nonzero rule has resulted in all enclosed regions being part of the interior. However, if the outline of the shape follows a slightly more convoluted path, the results can be a little more mixed, as Example 7-15 shows.

Example 7-15. Nonzero fill rule with more complex shape
<Polygon Fill="Yellow" Stroke="Blue" FillRule="Nonzero"
 Points="10,10 60,10 60,25 20,25 20,40 40,40 40,18 50,18 50,50 10,50" />

The results of Example 7-15 are shown in Figure 7-18. This illustrates that the Nonzero rule is not quite as straightforward as it may have first seemed.

Figure 7-18. Nonzero rule in action


The Nonzero rule is a bit of an oddity. It was popularized by PostScript, so most drawing systems support it, but it's not always easy to get useful results from a Polygon with this fill rule. It makes more sense in the context of the Path element, which supports multiple figures in a single shape.

7.2.7. Path

Path is by far the most powerful shape. All of the shapes we have looked at up to now have been supplied for convenience, because it is possible to draw all of them with a Path. Path also makes it possible to draw considerably more complex shapes than is possible with the previous shapes we have seen.

Like Polygon, Path has a FillRule property to control the fill rule. In place of the Points property, Path has a Data property. Its type is Geometry, which is an abstract base class. A Geometry object represents a particular shape. There are various concrete classes for representing different kinds of shapes. Three of these will sound rather familiar: RectangleGeometry, EllipseGeometry, and LineGeometry. These represent the same shapes that we saw earlier. So this Rectangle:

    <Rectangle Fill="Blue" Width="40" Height="80" />

is effectively shorthand for this Path:

    <Path Fill="Blue">
      <Path.Data>

        <RectangleGeometry Rect="0, 0, 40, 80" />
      </Path.Data>
    </Path>

At this point, you might be wondering when you would ever use RectangleGeometry, EllipseGeometry, or LineGeometry in a Path instead of the simpler Rectangle, Ellipse , and Line. The reason is that Path lets you use a special kind of geometry object called a GeometryGroup to create a shape with multiple geometries.

There is a significant difference between using multiple distinct shapes and having a single shape with multiple geometries. Let's look at an example (Example 7-16).

Example 7-16. Two Ellipses
<Canvas>
    <Ellipse Fill="Blue" Stroke="Black" Width="40" Height="80" />
    <Ellipse Canvas.Left="10" Canvas.Top="10" Fill="Blue" Stroke="Black"
             Width="20" Height="60" />
</Canvas>

This draws two ellipses, one on top of the other. They both have a black outline, so you can see the smaller one inside the larger one, as Figure 7-19 shows.

Figure 7-19. Two Ellipses


Since the Ellipse shape is just a simple way of creating an EllipseGeometry, the code in Example 7-16 is equivalent to the code in Example 7-17. (As you can see, using a Path is considerably more verbose. This is why the Ellipse and other simple shapes are provided.)

Example 7-17. Two Paths with EllipseGeometries
<Canvas>
    <Path Fill="Cyan" Stroke="Black">
        <Path.Data>
            <EllipseGeometry Center="20, 40" RadiusX="20" RadiusY="40" />
        </Path.Data>

    </Path>
    <Path Fill="Cyan" Stroke="Black">
        <Path.Data>
            <EllipseGeometry Center="20, 40" RadiusX="10" RadiusY="30" />
        </Path.Data>
    </Path>

</Canvas>

Because the code in Example 7-17 is equivalent to that in Example 7-16, it results in exactly the same output, as shown in Figure 7-19. So far, using geometries instead of shapes hasn't made a difference in the rendered results. This is because we are still using multiple shapes. So, we will now show how both ellipses can be put into a single Path and see how this affects the results. Example 7-18 shows the modified markup.

Example 7-18. One Path with two EllipseGeometries
<Canvas Canvas.Left="100">
    <Path Fill="Cyan" Stroke="Black">
        <Path.Data>
            <GeometryGroup>
                <EllipseGeometry Center="20, 40" RadiusX="20" RadiusY="40" />

                <EllipseGeometry Center="20, 40" RadiusX="10" RadiusY="30" />
            </GeometryGroup>
        </Path.Data>
    </Path>
</Canvas>

This version has just a single path. Its Data property contains a GeometryGroup. This allows any number of geometry objects to be added to the same path. Here we have added the two EllipseGeometry elements that were previously in two separate paths. The result, shown in Figure 7-20, is clearly different from the one in Figure 7-19there is now a hole in the middle of the shape. Because the default even-odd fill rule was in play, the smaller ellipse makes a hole in the larger one.

Figure 7-20. Path with two geometries


Shapes with holes can be created only by combining multiple figures into a single shape. You could try to get a similar effect to that shown in Figure 7-20 by drawing the inner Ellipse with a Fill color of White, but that trick fails to work as soon as you draw the shape on top of something else, as Figure 7-21 shows.

Figure 7-21. Spot the fake hole


You might be wondering if you could just draw the inner ellipse using the transparent color, but that doesn't work either. Drawing something as totally transparent has the same effect as drawing nothing at allthat's what transparency means. Only by knocking a hole in the shape can we see through it.

To understand why, think about the drawing process. When it renders our elements to the screen, WPF draws the items one after the other. It starts with whatever's at the backthe text in this case. Then it draws the shape on top of the textwhich will effectively obliterate the text that was underneath the shape. (It's still there in the element tree of course, so WPF can always redraw it later if you change or remove the shape.) Since you just drew over the text, you can't draw another shape on top to "undraw" a hole into the first shape. So if you want a hole in a shape, you'd better make sure that the hole is there before you draw the shape!


We have not yet looked at the most flexible geometry: PathGeometry . This can draw any shape that the Polyline or Polygon can represent, but it can draw many more besides.

A PathGeometry contains one or more PathFigure objects, and each PathFigure represents a single open or closed shape in the path. To define the shape of each figure's outline, you use a sequence of PathSegment objects.

PathGeometry's ability to contain multiple figures overlaps slightly with Path's ability to contain multiple geometries. This is just for convenienceif you need to make a shape where every piece will be a PathGeometry object, it is more compact to have a single PathGeometry with multiple PathFigures. If you just want to use simpler geometries like LineGeometry or RectangleGeometry, it is simpler to use a GeometryGroup and avoid PathGeometry altogether.


Example 7-19 shows a simple path. This contains just a single figure and draws a square.

Example 7-19. A square Path
<Path Fill="Cyan" Stroke="Black">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>

                <PathFigure>
                    <PathFigure.Segments>
                        <StartSegment Point="0,0" />
                        <LineSegment Point="50,0" />
                        <LineSegment Point="50,50" />
                        <LineSegment Point="0,50" />

                        <CloseSegment />
                    </PathFigure.Segments>
                </PathFigure>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>

</Path>

The result is shown in Figure 7-22. This seems like a vast amount of effort for such a simple resultwe've used 17 lines of markup to achieve what could have been done with a single Rectangle element. This is why WPF supplies classes for the simpler shapes and geometries. You don't strictly need any of them because you can use Path and PathGeometry instead, but they require much less effort. Normally, you would use Path only for more complex shapes.

Figure 7-22. A square Path


Even though Example 7-19 produces a very simple result, it illustrates most of the important features of a Path with a PathGeometry. As with all the previous examples, the geometry is in the path's Data property. The PathGeometry is a collection of PathFigures, so all of the interesting data is inside its Figures property. This example contains just one PathFigure, but you can add as many as you like. The shape of the PathFigure is determined by the items in its Segments property.

The sequence of segments always begins with a StartSegment, which determines the starting point of the path. This is then followed by one or more segments that determine the shape of the figure. In Example 7-19, these are all LineSegments because the shape has only straight edges, but several types of curve are also on offer. Finally, this example ends with a CloseSegment, indicating that this is a closed shape. If we wanted to create an open shape (like a Polyline) we would simply omit the final CloseSegment.

You might be wondering why LineSegments don't work like the Line shape. With a Line, we specify start and end points, as in Example 7-11. This seems simpler than LineSegment, which needs us to begin with StartSegment.

However, line segments in a PathFigure can't work like that because there cannot be any gaps in the outline of a figure. With the Line element, each Line is a distinct shape in its own right, but with a PathFigure, each segment is a part of the shape's outline. To define a figure fully and unambiguously, each segment must start off from where the previous one finished. This is why the LineSegment specifies only an end point for the line. All of the segment types work this way. Of course, the figure needs to start somewhere, which is why we always begin with a StartSegment. It is illegal to use StartSegment anywhere other than at the start of a figure.


Example 7-19 isn't very excitingit just uses straight line segments. We can create much more interesting shapes by using one of the curved segment types instead. Table 7-2 shows all of the segment types.

Table 7-2. Segment types

Segment type

Usage

StartSegment

Sets the starting point of the first segment of the figure.

CloseSegment

Indicates that this is a closed figure; where used, this must be the last segment.

LineSegment

Single straight line.

PolyLineSegment

Sequence of straight lines.

ArcSegment

Elliptical arc.

BezierSegment

Cubic Bézier curve.

QuadraticBezierSegment

Quadratic Bézier curve.

PolyBezierSegment

Sequence of cubic Bézier curves.

PolyQuadraticBezierSegment

Sequence of quadratic Bézier curves.


ArcSegment lets you add elliptical curves to the edge of a shape. ArcSegment is a little more complex to use than a simple LineSegment. As well as specifying the end point of the segment, we must also specify two radii for the ellipse with the Size property.

The ellipse size and the line start and end points don't provide enough information to define the curve unambiguously, because there are several ways to draw an elliptical arc given these constraints. Consider a segment with a particular start and end point, and a given size and orientation of ellipse. For this segment there will usually be two ways in which the ellipse can be positioned so that both the start and end point lie on the boundary of the ellipse, as Figure 7-23 shows. In other words, there will be two ways of "slicing" an ellipse with a particular line.

Figure 7-23. Potential ellipse positions


For each of these two ways of slicing the ellipse, there will be two resulting segments, a small one and a large one. This means that there are four ways in which the curve can be drawn between two points.

ArcSegment provides two flags enabling you to select which of the curves you require. LargeArc determines whether you get the larger or smaller slice size. SweepFlag chooses which side of the line the slice is drawn on. Example 7-20 shows markup illustrating all four combinations of these flags. It also shows the whole ellipse.

Example 7-20. ArcSegments
<Canvas>
    <Ellipse Fill="Cyan" Stroke="Black" Width="140" Height="60" />
    <Path Fill="Cyan" Stroke="Black" Canvas.Left="180">
        <Path.Data>
            <PathGeometry>

                <PathFigure>
                    <StartSegment Point="0,11" />
                    <ArcSegment Point="50,61" Size="70,30"
                                SweepFlag="False" LargeArc="False" />
                    <CloseSegment />
                </PathFigure>
                <PathFigure>

                    <StartSegment Point="30,11" />
                    <ArcSegment Point="80,61" Size="70,30"
                                SweepFlag="True" LargeArc="True" />
                    <CloseSegment />
                </PathFigure>
                <PathFigure>
                    <StartSegment Point="240,1" />

                    <ArcSegment Point="290,51" Size="70,30"
                                SweepFlag="False" LargeArc="True" />
                    <CloseSegment />
                </PathFigure>
                <PathFigure>
                    <StartSegment Point="280,1" />
                    <ArcSegment Point="330,51" Size="70,30"
                                SweepFlag="True" LargeArc="False" />

                    <CloseSegment />
                </PathFigure>
            </PathGeometry>
        </Path.Data>
    </Path>
</Canvas>


You may be wondering why the Ellipse has a width of 140 and a height of 60, which is double the Size of each ArcSegment. This is because the ArcSegment interprets the Size as the two radii of the ellipse, while the Width and Height properties on the Ellipse indicate the total size.


Figure 7-24 shows the results, and, as you can see, each of the shapes has one straight diagonal line and one elliptical curve. The straight-line edge has the same length and orientation in all four cases. The curved edge is from different parts of the same ellipse.

In Figure 7-24, the ellipse's axes are horizontal and vertical. Sometimes you will want to use an ellipse where the axes are not aligned with your main drawing axes. ArcSegment provides an XRotation property, allowing you to specify the amount of rotation required in degrees.

Figure 7-24. An ellipse and four arcs from that ellipse


Figure 7-25 shows four elliptical arcs . These use the same start and end points as Figure 7-24 and the same ellipse size. The only difference is that an XRotation of 45 degrees has been specified, rotating the ellipse before slicing it.

Figure 7-25. Four arcs from a rotated ellipse


There are two degenerate cases in which there will not be two ways of slicing the ellipse. The first is when the slice cuts the ellipse exactly in half. In this case, the LargeArc flag is irrelevant, because both slices are exactly the same size.

The other case is when the ellipse is too smallif the widest point at which the ellipse could be sliced is narrower than the segment is long, there is no way in which the segment can be drawn correctly. You should try to avoid this. (If you do make the ellipse too small, WPF seems to scale up the ellipse so that it is large enough, preserving the aspect ratio between the x- and y-axes.)


The remaining four curve types from Table 7-2BezierSegment, PolyBezierSegment, QuadraticBezierSegment, and PolyQuadraticBezierSegmentare all variations on the same theme. They all draw Bézier curves .

7.2.7.1. Bézier curves

Bézier curves are curved line segments joining two points based on a particular mathematical formula. It is not necessary to understand the details of the formula in order to use Bézier curves. What makes Bézier curves useful is that they offer a fair amount of flexibility in the shape of the curve. This has made them very popularmost vector drawing programs offer them. (If you'd like to understand the formula, http://mathworld.wolfram.com/BezierCurve.html and http://en.wikipedia.org/wiki/Bézier_curve both provide good descriptions.) Figure 7-26 shows a variety of Bézier curve segments.

Figure 7-26. Bézier curve segments


Each of the five lines shown in Figure 7-26 is a single BezierSegment . Bézier curves have become very widely used in graphics systems because of the wide variation in shapes that even a single segment can offer. They are also fairly straightforward to use.

As with all of the segment types, a BezierSegment starts from where the previous segment left off and defines a new end point. It also requires two control points to be defined, and it is these that determine the shape of the curve. Figure 7-27 shows the same curves again, but with the control points drawn on. It also shows lines connecting the control points to the segment end points, because this makes it easier to see how the control points affect the curve shapes.

Figure 7-27. Bézier curves with control points shown


The most obvious way in which the control points are influencing the shapes of the curves is that they determine the tangent. At the start and end of each segment, the direction in which the curve runs at that point is exactly the same as the direction of the line joining the start point to the corresponding control point.

There is a second, less obvious way in which control points work. The distance between the start or end point and its corresponding control point (i.e., the length of the straight lines added on Figure 7-27) also has an effect. This essentially determines how extreme the curvature is.

Figure 7-28 shows a set of Bézier curves similar to those in Figure 7-27. The tangents of both ends of the lines remain the same, but, in each case, the distance between the start point and the first control point is reduced to one quarter of what it was before, while the other is the same as before. As you can see, this reduces the influence of the first control point. In all four cases, the shape of the curve is dominated by the control point that is further from its endpoint.

Figure 7-28. Bézier curves with less extreme control points


Example 7-21 shows the markup for the second curve segment in Figure 7-27. The Point1 property determines the location of the first control pointthe one associated with the start point. Point2 positions the second control point. Point3 is the end point.

Example 7-21. BezierSegment
<StartSegment Point="0,50" />
<BezierSegment Point1="60,50" Point2="100,0" Point3="100,50" />

Flexible though Bézier curves are, you will rarely use just a single one. When defining shapes with curved edges, it is more common for a shape to have many Bézier curves defining its edge. WPF therefore supplies a PolyBezierSegment type, which allows multiple curves to be represented in a single segment. It defines a single Points property, which is an array of Point structures. Each Bézier curve requires three entries in this array: two control points and an end point. (As always, each curve starts from where the previous one left off.) Example 7-22 shows a sample segment with two curves. Figure 7-29 shows the results.

Example 7-22. PolyBezierSegment
<StartSegment Point="0,0" />
<PolyBezierSegment>
    <PolyBezierSegment.Points>
        <Point X="0" Y="10"/>
        <Point X="20" Y="10"/>

        <Point X="40" Y="10"/>
        <Point X="60" Y="10"/>
        <Point X="120" Y="15"/>
        <Point X="100" Y="50"/>
    </PolyBezierSegment.Points>
</PolyBezierSegment>


Figure 7-29. PolyBezierSegment


This markup is somewhat less convenient than simply using a sequence of BezierSegment elements. Fortunately, you can provide all of the point data in string form. This is equivalent to Example 7-22:

    <PolyBezierSegment Points="0,10 20,10 40,10 60,10 120,15 100,50" />


Also, if you are generating coordinates from code, dealing with a single PolyBezierSegment and passing it an array of Point data is easier than working with lots of individual segments.

Cubic Bézier curves provide a lot of control over the shape of the curve. However, you might not always want that level of flexibility. The QuadraticBezierSegment uses a simpler equation that uses just one control point to define the shape of the curve. This does not offer the same range of curve shapes as a cubic Bézier curve, but if all you want is a simple shape, this reduces the number of coordinate pairs you need to provide by one third.

QuadraticBezierSegment is similar in use to the normal BezierSegment. The only difference is that it has no Point3 propertyjust Point1 and Point2. Point1 is the shared control point, and Point2 is the end point. PolyQuadraticBezierSegment is the multi-curve equivalent. You use this in exactly the same way as PolyBezierSegment, except you only need to provide two points for each segment.

7.2.7.2. Combining shapes

Path has one more trick up its sleeve that we have not yet examined. It is capable of combining geometries to form new geometries. This is different from adding two geometries to a Pathit combines pairs of geometries in a way that forms a single geometry with a whole new shape.

Example 7-23 and Example 7-24 each define paths, both of which make use of the same RectangleGeometry and EllipseGeometry. The difference is that Example 7-23 puts both into a GeometryGroup, while Example 7-24 puts them into a CombinedGeometry .

Example 7-23. Multiple geometries
<Path Fill="Cyan" Stroke="Black">
    <Path.Data>
        <GeometryGroup>
            <RectangleGeometry Rect="0,0,50,50" />

            <EllipseGeometry Center="50,25" RadiusX="30" RadiusY="10" />
        </GeometryGroup>
    </Path.Data>
</Path>

Example 7-24. Combined geometries

<Path Fill="Cyan" Stroke="Black">
    <Path.Data>
        <CombinedGeometry CombineMode="Exclude">
            <CombinedGeometry.Geometry1>
                <RectangleGeometry Rect="0,0,50,50" />
            </CombinedGeometry.Geometry1>

            <CombinedGeometry.Geometry2>
                <EllipseGeometry Center="50,25" RadiusX="30" RadiusY="10" />
            </CombinedGeometry.Geometry2>
        </CombinedGeometry>
    </Path.Data>
</Path>


Figure 7-30 shows the results of Example 7-23 and Example 7-24. While the GeometryGroup has resulted in a shape with multiple figures, the CombinedGeometry has produced a single figure. The ellipse geometry has taken a bite out of the rectangle geometry. This is just one of the ways in which geometries can be combined. The CombineMode property determines which is used, and Figure 7-31 shows all five available modes.

Figure 7-30. Grouping and combining geometries


Figure 7-31. Combine modes: Union, Intersect, Xor, Exclude, and Complement


The combine modes have the following effects:


Union

Builds a shape where any point that was inside either of the two original shapes will also be inside the new shape.


Intersect

Creates a shape where only points that were inside both shapes will be in the new shape.


Xor

Creates a shape where points that were in one shape or the other but not both will be in the new shape.


Exclude

Creates a shape where points inside the first shape but not inside the second will be included.


Complement

Builds a shape where points inside the second but not inside the first shape will be included in the new shape.

7.2.7.3. Data property text format

We have now looked at all of the features that Path has to offer. As you have seen, we can end up with some pretty verbose markup. Fortunately, the Path element offers a shorthand mechanism that allows most of the features we have seen to be exploited without having to type quite so much.

So far, we have been setting the Data property using XAML's property-element syntax (see Appendix A for more details on this syntax). However, we can supply a string instead. Example 7-25 shows both techniques. As you can see, the string form is some 16 lines shorter.

Example 7-25. Path.Data as text
<!-- Longhand -->
<Path Fill="Cyan" Stroke="Black">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>

                <PathFigure>
                    <PathFigure.Segments>
                        <StartSegment Point="0,0" />
                        <LineSegment Point="50,0" />
                        <LineSegment Point="50,50" />
                        <LineSegment Point="0,50" />

                        <CloseSegment />
                    </PathFigure.Segments>
                </PathFigure>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>

</Path>

<!-- Shorthand -->
<Path Fill="Cyan" Stroke="Black" Data="M 0,0 L 50,0 50,50 0,50 Z" />

The syntax for the text form of the Path.Data property is simple. The string must contain a sequence of commands. A command is a letter followed by some numeric parameters. The number of parameters required is determined by the chosen command. Lines require just a coordinate pair. Curves require more data.

If you omit the letter, the same command will be used as last time. For example, Example 7-25 uses the L commandthis is short for "Line" and represents a LineSegment. This requires only two numbers, the coordinates of the line end point. And yet in our example, there are six numbers. This simply indicates that there are three lines in a row. Table 7-3 lists the commands, their equivalent segment type, and their usage.

Table 7-3. Path.Data commands

Command

Command name

Segment type

Parameters

M (or m)

Move

StartSegment

Coordinate pair: start point

L (or l)

Line

LineSegment

Coordinate pair: end point

H (or h)

Horizontal Line

LineSegment

Single coordinate: end x-coordinate (y-coordinate will be the same as before)

V (or v)

Vertical Line

LineSegment

Single coordinate: end y-oordinate (x-coordinate will be the same as before)

C (or c)

Cubic Bézier Curve

BezierSegment

Three coordinate pairs: two control points and end point

Q (or q)

Quadratic Bézier Curve

QuadraticBezierSegment

Two coordinate pairs: control point and end point

S (or s)

Smooth Bézier Curve

BezierSegment

Two coordinates: second control point and end point (first control point generated automatically)

A (or a)

Elliptical Arc

ArcSegment

Seven numbers: Size pair, Rotation, LargeArc, SweepFlag, end point coordinate pair

Z (or z)

Close path

CloseSegment

None


The M command gets special treatment. It is illegal to use a StartSegment anywhere other than as the first segment of a PathFigure. If you use an M command in the middle of the data, this is taken to mean that you would like to start a new PathFigure. This enables multiple figures to be represented in this compact text format.

Notice that there are two ways of specifying a BezierSegment. The C command lets you provide all of the control points. The S command generates the first control point for youit looks at the previous segment, and makes the first control point a mirror image of the previous one. This ensures that the segment's tangent aligns with the previous segment's tangent, resulting in a smooth join between the lines.

All of these commands can be used in two ways. You can specify the command in either uppercase or lowercase. In the uppercase form, coordinates are relative to the position of the Path element. If the command is lowercase, the coordinates are taken to be relative to the end point of the previous segment in the path.

We have now examined all of the shapes on offer, but so far, we have been rather unadventurous in our choice of fills and outlines for these shapes. We have used nothing but standard named colors and simple outline styles. WPF allows us a much greater variety of drawing styles through its brush and pen classes.


©2008 FAQ - WPF Labs - Discuss - Terms of Use - Privacy Policy - About WPF
- Interview Questions - Sharepoint Articles - Interview Questions Resource Library - All about LINQ - MS Knowledgebase Articles - Electronics and Hardware discussions