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?

Brushes and Pens

7.3. Brushes and Pens

In order to draw a shape onscreen, WPF needs to know how you would like that shape to be colored in and how its outline should be drawn. WPF provides several Brush types supporting a variety of painting styles. The Pen class augments this to provide information about stroke thickness, dash patterns, and the like.

In this section, we will look at all of the available brush types and the Pen class. However, since all brushes and pens are ultimately about deciding what colors to use where, and how they are combined, we must first look at how colors are represented.

7.3.1. Color

WPF uses the Color structure in the System.Windows.Media namespace to represent a color. Note that if you have worked with Windows Forms, ASP.NET, or GDI+ in the past, this is not the same structure those technologies use. They use the Color structure in the System.Drawing namespace. WPF introduces this new Color structure because it can work with floating-point color values, enabling much higher color precision, and greater flexibility.

The Color structure uses four numbers, or channels, to represent a color. These channels are red, green, blue, and alpha. Red, green, and blue channels are the traditional way of representing color in computer graphics. (This is because color screens work by mixing together these three primary colors.) A value of zero indicates that the color component is not present at all; zero on all three channels corresponds to black. The alpha channel represents the level of opacitya Color can be opaque, completely transparent, or anywhere in between these two extremes. WPF's composition engine supports transparency fully, so anything can be drawn with any level of transparency. Zero is used to represent complete transparency.

Windows has traditionally used 24 bits of color information, 8 bits per channel, to represent "true" or "full" color, and 32 bits for full color with transparency. This is just about sufficient for the average computer screenthe color and brightness range of normal computer displays is such that 24 bits of color has always been adequate for most purposes. However, for many imaging applications, this is not sufficient. For example, film can accommodate a much wider range of brightness than a computer screen, and 24-bit color is simply not adequate for graphics work with film as its output medium. The same is true for many medical imaging applications. And even for computer or video images, 24-bit color can cause problemsif images are going through many stages of processing, these can amplify the limitations of 24-bit source material.

WPF therefore supports a much higher level of detail in its representation of color. Each color channel uses 16 bits instead of 8. The Color structure still supports the use of 8-bit channels where required, because a lot of imaging software depends on such a representation. Color exposes the 8-bit channels through the A, R, G, and B properties, which accept values in the range 0 to 255.[*] The higher definition representations are available through the ScA, ScR, ScG, and ScB properties, which present the channels as single-precision floating-point values ranging from 0 to 1.

[*] The old GDI+ Color structure exposed 8-bit properties of the same names, which may be useful if you need to port code.

The "Sc" in the ScA, ScR, ScG, and ScB properties refers to the fact that they support the standard "Extended RGB colour spacescRGB" color space defined in the IEC 61966-2-2 specification. (This is an international specification, hence the "u" in "colour.") The "sc" is short for "scene" because this is nominally a scene-referred color space. This means that color values in the scRGB space represent the colors of the original image. This is different from how computer images are often storedtraditionally we have used output-referred color spaces, where the color values do not need to be mapped before being displayed on the target device.

Output-referred color spaces are efficient to work with, as long as they happen to target the output device type you are working with. However, scene-referred color spaces preserve all of the information that was available at the point at which the image was generated or captured. For highly accurate color representation, scene-referred models are therefore clearly better, even though they are slightly less efficient to work with.


There is also a Colors class. This provides a set of standard named colors, with all the old favorites such as PapayaWhip, BurlyWood, LightGoldenrodYellow, and Brown.

You cannot use a Color directly for drawing. To draw, you need either a Brush or a Pen.

7.3.2. SolidColorBrush

SolidColorBrush is the simplest brush. It uses one color across the whole area being painted. It has just one property, Color. Note that this color is allowed to use transparency, despite what the word "solid" suggests.

We have already been using the SolidColorBrush extensively even though we have not yet referred to it by name. This is because WPF creates this kind of brush if you specify the name of a color in markupif you work mostly with markup, you very rarely need to specify that you require a SolidColorBrush, because you'll get one by default. (The only reason you would normally specify it in full is if you want to use data binding with the brush's properties.) Consider this example:

    <Rectangle Fill="Yellow" Width="100" Height="20" />

The XAML compiler will recognize Yellow as one of the standard named colors from the Colors class, and will supply a suitable SolidColorBrush. (See Appendix A for more information on how XAML maps from strings to property values.) It does not need to create the brush, because there is a Brushes class, providing a set of brushes for each of the named colors in Colors.

You will also be provided with a SolidColorBrush if your markup uses a numeric color value. Example 7-26 shows various examples of numeric colors. They all begin with a # symbol and contain hexadecimal digits. A three-digit number is taken to be one digit each of red, green, and blue. A four-digit number is interpreted as alpha, red, green, and blue. These are compact formats, but provide just four bits per channel. Six- or eight-digit numbers allow eight bits per channel for RGB or ARGB, respectively. (To exploit the full 16-bit accuracy of scRGB, you would need to set the property using property-element syntaxthere is no text shorthand. See Appendix A for more information on XAML's property-element syntax.)

Example 7-26. Numeric color values
<Rectangle Fill="#8f8"      Width="100" Height="20" />

<Rectangle Fill="#1168ff"   Width="50" Height="40" />
<Rectangle Fill="#8ff0"     Width="130" Height="10" />
<Rectangle Fill="#72ff8890" Width="70" Height="30" />

The SolidColorBrush is lightweight and straightforward. However, it makes for fairly flat-looking visuals. WPF offers some more interesting brushes if you want to make your user interface look a little more appealing.

7.3.3. LinearGradientBrush

With a LinearGradientBrush, the painted area transitions from one color to another, or even through a sequence of colors. Figure 7-32 shows a simple example.

Figure 7-32. LinearGradientBrush


This brush fades from black to white, starting at the top-left corner and finishing at the bottom-right corner. The fade always runs in a straight lineyou cannot do curved transitions, hence the name "linear." Example 7-27 shows the markup for Figure 7-32.

Example 7-27. Using a LinearGradientBrush
<Rectangle Width="80" Height="60">
    <Rectangle.Fill>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="Black" Offset="0" />

                <GradientStop Color="White" Offset="1" />
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Rectangle.Fill>
</Rectangle>

The StartPoint and EndPoint properties indicate where the color transition begins and ends. These coordinates are relative to the area being filled, so 0,0 is the top left and 1,1 is the bottom right, as shown in Figure 7-33. (Note that if the brush is painting an area that is narrow or wide, the coordinate system is squashed accordingly.)

Figure 7-33. Fill coordinate system


Example 7-27 uses the property-element syntax to initialize the brush. In this particular example, this wasn't strictly necessary, because XAML supports a more compact syntax. This is exactly equivalent to Example 7-27:

    <Rectangle Width="80" Height="60" Fill="LinearGradient 0,0 1,1 Black White" />

This compact syntax allows the most important aspects to be set. The two pairs of numbers correspond to the StartPoint and EndPoint properties, and the remaining two items are color names. If you want the gradient to be vertical or horizontal, you can use an even simpler syntax:

    <Rectangle Width="80" Height="60" Fill="VerticalGradient Black White" />
    <Rectangle Width="80" Height="60" Fill="HorizontalGradient Black White" />

In all of these string representations, the start and end color are specified. These correspond to the GradientStop elements in Example 7-27. Notice that in the fully expanded compound-property version, each GradientStop has an Offset property as well as a Color. This enables the more verbose style to achieve something that the string representations cannot. It allows the fill to pass through multiple colors. Example 7-28 shows a LinearGradientBrush with multiple colors.

Example 7-28. Multiple gradient stops
<Rectangle Width="80" Height="60">
  <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <LinearGradientBrush.GradientStops>
        <GradientStop Color="Black" Offset="0" />

        <GradientStop Color="Orange" Offset="0.2" />
        <GradientStop Color="Red" Offset="0.4" />
        <GradientStop Color="Black" Offset="0.6" />
        <GradientStop Color="Blue" Offset="0.8" />
        <GradientStop Color="White" Offset="1" />
      </LinearGradientBrush.GradientStops>

    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

The result is shown in Figure 7-34. Note that the shorthand string syntaxes shown earlier do not support multiple color valuesyou have to use the full property-element syntax if you want this effect.

Figure 7-34. Multiple gradient stops (Color Plate 22)


LinearGradientBrush is often used to add a feeling of depth to a user interface. Example 7-29 shows a typical example. It uses just two shapesa pair of rounded Rectangle elements. (The Grid doesn't contribute directly to the appearance. It is there to make it easy to resize the graphicchanging the grid's Width and Height will cause both rectangles to resize appropriately.) The second rectangle's gradient fill fades from a partially transparent shade of white to a completely transparent color, which provides an interesting visual effect.

Example 7-29. Simulating lighting effects with linear fills
<Grid Width="80" Height="26">
    <Grid.RowDefinitions>
        <RowDefinition Height="2*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"
               Fill="VerticalGradient Green DarkGreen"
               Stroke="VerticalGradient Black LightGray" />
    <Rectangle Margin="3,2" RadiusX="8" RadiusY="12"
               Fill="VerticalGradient #dfff #0fff" />
</Grid>

Figure 7-35 shows the result. This is an extremely simple graphic, containing just two shapes. The use of gradient fills has added an impression of depth that these shapes would otherwise not have conveyed.

Figure 7-35. Simple lighting effects with linear fills (Color Plate 23)


7.3.4. RadialGradientBrush

RadialGradientBrush is very similar to LinearGradientBrush. Both allow transitions through a series of colors. But while LinearGradientBrush paints these transitions in a straight line, the RadialGradientBrush fades from a starting point out to an elliptical boundary. This opens up more opportunities for making your user interface appear less flat, as shown in Example 7-30.

Example 7-30. Using a RadialGradientBrush
<Rectangle Width="200" Height="150">
    <Rectangle.Fill>
        <RadialGradientBrush Center="0.5,0.4" RadiusX="0.3" RadiusY="0.5"
                             GradientOrigin="0.25,0.4">
            <RadialGradientBrush.GradientStops>
                <GradientStop Color="White" Offset="0" />

                <GradientStop Color="DarkBlue" Offset="1" />
            </RadialGradientBrush.GradientStops>
        </RadialGradientBrush>
    </Rectangle.Fill>
</Rectangle>

The RadialGradientBrush takes a list of GradientStop objects to determine the colors that the fill runs through, just like LinearGradientBrush. This example uses the RadiusX and RadiusY properties to determine the size of the elliptical boundary, and the Center property to set the position of the ellipse. The values chosen here make the fill boundary fit entirely into the shape, as Figure 7-36 shows. The area of the shape that falls outside of this boundary is filled with the color of the final GradientStop. Notice that the focal point of the fill is to the left. This is because the GradientOrigin has been set. (By default, the focal point is in the center of the ellipse.)

Figure 7-36. Simple radial fill (Color Plate 24)


Example 7-30 makes it easy to see the effects of the properties of the RadialGradientBrush, but it's not a very exciting example. Example 7-31 shows something a little more adventurous. It is similar to Example 7-29it uses a small number of shapes with gradient fills to convey a feeling of depth and reflection, but this time using radial fills.

Example 7-31. Radial gradient fills
<Grid Width="16" Height="16" Margin="0,0,5,0" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="10*" />
        <ColumnDefinition Width="*" />

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="20*" />
        <RowDefinition Height="6*" />
    </Grid.RowDefinitions>



    <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" Margin="0.5">
        <Ellipse.Fill>
            <RadialGradientBrush Center="0.5,0.9" RadiusX="0.7" RadiusY="0.5">
                <RadialGradientBrush.GradientStops>
                    <GradientStop Color="PaleGreen" Offset="0" />

                    <GradientStop Color="Green" Offset="1" />
                </RadialGradientBrush.GradientStops>
            </RadialGradientBrush>
        </Ellipse.Fill>
    </Ellipse>
    <Ellipse Grid.Row="1" Grid.Column="1">

        <Ellipse.Fill>
            <RadialGradientBrush Center="0.5,0.1" RadiusX="0.7" RadiusY="0.5">
                <RadialGradientBrush.GradientStops>
                    <GradientStop Color="#efff" Offset="0" />
                    <GradientStop Color="Transparent" Offset="1" />
                </RadialGradientBrush.GradientStops>

            </RadialGradientBrush>
      </Ellipse.Fill>
    </Ellipse>
    <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3"
             Stroke="VerticalGradient Gray LightGray" />

</Grid>

This time, three ellipses have been used, two with RadialGradientBrush fills and one with a LinearGradientBrush stroke. The fill in the first ellipse creates the glow at the bottom of the drawing. The second adds the reflective highlight at the top. The third draws a bezel around the outside. The result is shown in Figure 7-37. The radial fills suggest a curved surface and give the graphic a slightly translucent look.

Figure 7-37. Radial fills (Color Plate 25)


7.3.5. ImageBrush, and VisualBrush, DrawingBrush

It is often useful to fill shapes with a pattern or image of some kind. WPF provides three brushes that allow us to use whatever graphics we choose as a brush. The ImageBrush lets us paint with a bitmap. With DrawingBrush we use a scalable drawing. VisualBrush allows us to use any markup at all as the brush imagewe can, in effect, use one piece of our user interface to paint another.

All of these brushes have a certain amount in common, so they all derive from the same base class, TileBrush .

7.3.5.1. TileBrush

ImageBrush, DrawingBrush, and VisualBrush all paint using some form of source picture. Their base class, TileBrush, decides how to stretch the source image to fill the available space, whether to repeat ("tile") the image, and how to position the image within the shape. (TileBrush is an abstract base class, so you cannot use it directly. It exists to define the API common to the ImageBrush, DrawingBrush, and VisualBrush.)

Figure 7-38 shows the default TileBrush behavior. This figure shows three rectangles so that you can see what happens when the brush is made narrow or wide, as well as the effect when the brush shape matches the target-area shape. All three are rectangles painted with an ImageBrush specifying nothing more than the image.

Figure 7-38. Default stretching and placement (Stretch.Fill)


While Figure 7-38 was drawn with an ImageBrush, the behavior would be exactly the same for any of the tile brushes. We'll look at the ImageBrush in more detail shortly, but for now Example 7-32 shows the basic usage.

Example 7-32. Using an ImageBrush
<Rectangle>
    <Rectangle.Fill>
        <ImageBrush ImageSource="Images\Moggie.jpg" />
    </Rectangle.Fill>
</Rectangle>


The brush has stretched the source image to fill the available space. We can change this behavior by modifying the brush's Stretch property. It defaults to Fill, but we can show the image at its native size by specifying None, as Example 7-33 shows.

Example 7-33. Specifying a Stretch of None
<Rectangle>
    <Rectangle.Fill>
        <ImageBrush ImageSource="Images\Moggie.jpg" Stretch="None" />
    </Rectangle.Fill>
</Rectangle>

This preserves the aspect ratio, but if the image is large, it will simply be cropped to fit the space available, as Figure 7-39 shows.

Figure 7-39. Stretch.None


For displaying images, you may be more likely to want to stretch the image to match the available space without distorting the aspect ratio. TileBrush supports this with the Uniform stretch mode, shown in Figure 7-40. This scales the source image down so that it fits entirely within the space available.

Figure 7-40. Stretch.Uniform


The Uniform stretch mode typically results in the image being made smaller than the area being filled. The default behavior is to paint the image wherever the alignment properties specify and to leave the remainder of the shape transparent. However, you can choose other behaviors for the spare space with the TileMode property. The default is None, but if you specify Tile, as in Example 7-34, the image will be repeated to fill the space available.

Example 7-34. Specifying a Stretch and a TileMode
<Rectangle>
    <Rectangle.Fill>
        <ImageBrush ImageSource="Images\Moggie.jpg"
                    Stretch="Uniform" TileMode="Tile" />
    </Rectangle.Fill>
</Rectangle>

Figure 7-41 shows the effect of the Uniform stretch mode and the Tile tile mode combined. There is one potential problem with tiling. It can often be very obvious where each repeated tile starts. If your goal was simply to fill in an area with a texture, these discontinuities can jar somewhat. To alleviate this, TileBrush supports three other modes of tiling: FlipX, FlipY, and FlipXY. These alternate mirror images, as shown in Figure 7-42. Although mirroring can reduce the discontinuity between tiles, for some source images it can change the look of the brush quite substantially.

Figure 7-41. Stretch="Uniform" and TileMode="Tile"


Figure 7-42. TileMode="FlipXY"


An alternative to tiling is to scale the image so that it completely fills the space available while preserving the aspect ratio, cropping in one dimension if necessary. The UniformToFill stretch mode does this and is shown in Figure 7-43.

UniformToFill is most appropriate if you are filling an area with some non-repeating textured pattern, because it guarantees it will paint the whole area. It is probably less appropriate if your goal is simply to display a pictureas Figure 7-43 shows, this stretch mode will crop images where necessary. If you want to show the whole picture, Uniform is the best choice.

Figure 7-43. Stretch.UniformToFill


All of the stretch modes except for Fill present an extra question: how should the image be positioned? With None and UniformToFill, cropping occurs, so WPF needs to decide which part of the image to show. With Uniform, the image may be smaller than the space being filled, so WPF needs to decide where to put it.

Images are centered by default. In the examples where the image has been cropped (Figures 7-39 and 7-43) the most central parts are shown. In the case of Uniform, where the image is smaller than the area being painted, it has been placed in the middle of that area (Figure 7-40). You can change this with the AlignmentX and AlignmentY properties. These can be set to Left, Middle, or Right, and Top, Middle, or Bottom, respectively. Example 7-35 shows the UniformToFill stretch mode again, but this time with alignments of Left and Bottom. Figure 7-44 shows the results

Example 7-35. Specifying a stretch and alignment
<Rectangle>
    <Rectangle.Fill>
        <ImageBrush ImageSource="Images\Moggie.jpg" Stretch="UniformToFill"
                    AlignmentX="Left" AlignmentY="Bottom" />
    </Rectangle.Fill>
</Rectangle>


Figure 7-44. Stretch.UniformToFill, bottom-left aligned


The stretch and alignment properties are convenient to use, but they do not allow you to choose to focus on any arbitrary part of the image or choose specific scale factors. TileBrush supports these features through the Viewbox, Viewport, and ViewportUnits properties.

The Viewbox property chooses the portion of the image to be displayed. By default, this property is set to encompass the whole image, but you can change it to focus on a particular part. Figure 7-45 shows the UniformToFill stretch mode, but with a Viewbox set to zoom in on the front of the car.

Figure 7-45. Stretch.UniformToFill with Viewbox


As Example 7-36 shows, the Viewbox is specified as four numbers. The first two are the coordinates of the upper-left-hand corner of the Viewbox, and the second two are the width and height of the box. These coordinates are relative to the source image. In this case, because an ImageBrush is being used, these are coordinates in the source bitmap. In the case of a DrawingBrush or VisualBrush, the Viewbox would use the coordinate system of the source drawing.

Example 7-36. Specifying a Viewbox
<ImageBrush Stretch="UniformToFill" Viewbox="593,250,200,200"
            ImageSource="Images\Moggie.jpg" />

While Viewbox lets you choose which portion of the source image you would like to focus on, Viewport lets you choose where that image should end up in the brush. Its functionality overlaps with the alignment properties, but Viewport allows much more control.

Figure 7-46 illustrates the relationship between Viewbox and Viewport. On the left, we see the source imagea bitmap in this case, but it could also be a drawing or visual tree. The Viewbox specifies an area of this source image. On the right, we see the brush. The Viewport specifies an area within this brush. WPF will scale and position the source image so that the area specified in Viewbox ends up being painted into the area specified by Viewport.

The Viewport does not specify the extent of the brush. As Figure 7-46 shows, the brush does not need to be the same size as the Viewport. Nor will the brush be clipped to be the size of the Viewport. All the Viewport and Viewbox do is establish the scale and position at which the source image will be drawn in the target brush. Example 7-37 shows Viewport and Viewbox settings that correspond to the areas highlighted in Figure 7-46.

Figure 7-46. Meaning of Viewbox and Viewport


Example 7-37. Using Viewbox and Viewport
<ImageBrush 
 Viewbox="380,285,308,243"
            Viewport="0.1,0.321,0.7, 0.557"
            ImageSource="Images\Moggie.jpg" />

Notice in Example 7-37 that while the Viewbox coordinates are relative to the source image, the Viewport uses numbers between 0 and 1. By default, the Viewport coordinate system is based on the full size of the brush, with 0,0 being at the top left and 1,1 being at the bottom right. This means that the area of the image shown by the brush will always be the same, regardless of the brush size. This results in a distorting behavior similar to the default StretchMode of Fill, as shown in Figure 7-47. (In fact the Fill stretch mode is equivalent to setting the Viewbox to be the size of the source image, and the Viewport to be "0,0,1,1".)

You can specify different units for the Viewport with the ViewportUnits property. The default is RelativeToBoundingBox, but if you change it to Absolute, the Viewport is measured using the user interface's coordinate system.

Remember that all of this scaling and positioning functionality is common to all of the brushes derived from TileBrush. We will now look at the features specific to the individual brushes.

7.3.5.2. ImageBrush

ImageBrush paints areas of the screen using a bitmap. The ImageBrush was used to create all of the pictures in the previous section. This brush is straightforwardyou simply need to tell it what bitmap to use with the ImageSource property, as Example 7-38 shows.

Figure 7-47. Viewbox and Viewport in use


Example 7-38. Using an ImageBrush
<Rectangle>
    <Rectangle.Fill>
        <ImageBrush ImageSource="Images\Moggie.jpg" />
    </Rectangle.Fill>

</Rectangle>

To make a bitmap file available to the ImageBrush, simply add one to your project in Visual Studio 2005. The file in Example 7-38 was in a subdirectory of the project called Images. The bitmap must be built into the project as a resource. To do this, select the bitmap file in Visual Studio 2005's Solution Explorer and then in the Properties panel, make sure the Build Action property is set to Resource. This embeds the bitmap into the executable, enabling the ImageBrush to find it at runtime. (See Chapter 6 for more information on how binary resources are managed.) Alternatively, you can specify an absolute URL for this propertyyou could, for example, display an image from a web site.

ImageBrush is quite happy to deal with images with a transparency channel (also known as an "alpha" channel). Not all image formats support partial transparency, but some, such as the PNG and BMP formats, can. (And to a lesser extent, GIF can do so as well. It only supports fully transparent or fully opaque pixels. This is effectively a 1-bit alpha channel.) Where an alpha channel is present, the ImageBrush will honor it.


7.3.5.3. DrawingBrush

The ImageBrush is convenient if you have a bitmap you need to paint with. However, bitmaps do not fit in well with resolution independence. The ImageBrush will scale bitmaps correctly for your screen's resolution, but bitmaps tend to become blurred when scaled. DrawingBrush does not suffer from this problem, because you usually provide a scalable vector image as its source. This enables a DrawingBrush to remain clear and sharp at any size and resolution.

The vector image is represented by a Drawing object. This is an abstract base class. Shapes can be drawn with a GeometryDrawing this allows you to construct drawings using all of the same geometry elements supported by Path. You can also use bitmaps and video with ImageDrawing and VideoDrawing . Text is supported with GlyphRunDrawing . Finally, you can combine these together using the DrawingGroup .

Even if you use nothing but shapes, you will still probably want to group the shapes together with a DrawingGroup. Each GeometryDrawing is effectively equivalent to a single Path, so if you want to draw using different pens and brushes, or if you want your shapes to overlap rather than combine, you will need to use multiple GeometryDrawing elements. Example 7-39 shows a Rectangle using a DrawingBrush for its Fill.

Example 7-39. Using DrawingBrush
<Rectangle Width="80" Height="30">
    <Rectangle.Fill>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <DrawingGroup>

                    <DrawingGroup.Children>
                        <GeometryDrawing Brush="VerticalGradient Green DarkGreen">
                            <GeometryDrawing.Pen>
                                <Pen Thickness="0.02"
                                     Brush="VerticalGradient Black LightGray" />
                            </GeometryDrawing.Pen>
                            <GeometryDrawing.Geometry>

                                <RectangleGeometry RadiusX="0.2" RadiusY="0.5"
                                                   Rect="0.02,0.02,0.96,0.96" />
                            </GeometryDrawing.Geometry>
                        </GeometryDrawing>
                        <GeometryDrawing Brush="VerticalGradient #dfff #0fff">
                            <GeometryDrawing.Geometry>
                                <RectangleGeometry RadiusX="0.1" RadiusY="0.5"
                                                   Rect="0.1,0.07,0.8,0.5" />

                            </GeometryDrawing.Geometry>
                        </GeometryDrawing>
                    </DrawingGroup.Children>
                </DrawingGroup>
            </DrawingBrush.Drawing>
        </DrawingBrush>

    </Rectangle.Fill>
</Rectangle>

The brush in Example 7-39 paints the same visuals seen earlier in Figure 7-35. Because each of the rectangular elements that make up the drawing uses different linear gradient fills, they both get their own GeometryDrawing, nested inside a DrawingGroup.

With a DrawingBrush, the Viewbox defaults to 0,0,1,1. All of the coordinates and sizes in Example 7-39 are relative to this coordinate system. If you would prefer to work with coordinates over a wider range, you can simply set the Viewbox to the range you require. We already saw how to use the Viewbox in Example 7-36. The only difference with DrawingBrush is that you're using it to indicate an area of the drawing, rather than a bitmap.

Note that we can use the Viewbox to focus on some subsection of the picture, just as we did earlier with the ImageBrush. We can modify the DrawingBrush in Example 7-39 to use a smaller Viewbox, as shown in Example 7-40.

Example 7-40. Viewbox and DrawingBrush
<DrawingBrush Viewbox="0.5,0,0.5,0.25">

The result is that most of the drawing is now outside of the Viewbox, so the brush only shows a part of the whole drawing, as Figure 7-48 shows.

Figure 7-48. DrawingBrush with small Viewbox


DrawingBrush is extremely powerful, as it lets you use more or less any graphics you like as a brush, and because it is vector based, the results remains crisp at any scale. It does have one drawback if you are using it from markup, though: it is somewhat cumbersome to use from XAML. Consider that Example 7-39 produces the same appearance as Example 7-29, but these examples are 28 lines and 10 lines long, respectively.

The DrawingBrush is much more verbose because it requires us to work with geometry objects rather than higher-level constructs such as the Grid or Rectangle used in Example 7-29. (Note that this problem is less acute when using this brush from code, where the higher-level objects are no more convenient to use than geometries. So this is really only a XAML issue.) Fortunately, VisualBrush allows us to paint with these higher-level elements.

7.3.5.4. VisualBrush

The VisualBrush can paint with the contents of any element derived from Visual. Since Visual is the base class of all WPF user-interface elements, this means that in practice you can plug any markup you like into a VisualBrush. Example 7-41 shows a Rectangle filled using a VisualBrush.

Example 7-41. Using a VisualBrush
<Rectangle Width="80" Height="30">
    <Rectangle.Fill>
        <VisualBrush Viewbox="0,0,80,26">
            <VisualBrush.Visual>
                <Grid Width="80" Height="26">

                    <Grid.RowDefinitions>
                        <RowDefinition Height="2*" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"
                                Fill="VerticalGradient Green DarkGreen"
                                Stroke="VerticalGradient Black LightGray" />
                    <Rectangle Margin="3,2" RadiusX="8" RadiusY="12"
                                Fill="VerticalGradient #dfff #0fff" />

                </Grid>
            </VisualBrush.Visual>
        </VisualBrush>
    </Rectangle.Fill>
</Rectangle>

The brush's visuals in Example 7-41 have been copied directly from Example 7-29, resulting in a much simpler brush than the equivalent DrawingBrush. (The results look exactly the same as Figure 7-35the whole point of the VisualBrush is that it paints areas to look just like the visuals it wraps.)

You might be wondering why on earth you would ever use a DrawingBrush when VisualBrush is so much simpler. One reason we have both is that DrawingBrush is more efficienta drawing doesn't carry the overhead of a full FrameworkElement for every drawing primitive. Although it takes more effort to create a DrawingBrush, it consumes fewer resources at runtime. If you want your user interface to have particularly intricate visuals, the DrawingBrush will enable you to do so with lower overheads. If you plan to use animation, this low overhead may translate to smoother-looking animations.

VisualBrush makes it very easy to create a brush that looks exactly like some part of your user interface. You could use this to create effects such as reflections, or to make the user interface appear to rotate in 3-D. (This goes beyond the scope of this book.)

7.3.6. Pen

Brushes are used to fill the interior of a shape. To draw the outline of a shape, WPF needs a little more informationnot only does it need a brush in order to color in areas of the screen, it also needs to know how thick you would like the line to be drawn and whether you want a dash pattern and/or end caps. The Pen class provides these.

A Pen is always based on a brush, meaning that all of the drawing effects we've seen so far can be used when drawing outlines. The brush is set using the Brush property.

Remember that if you are working with any of the high-level shape elements, you will not work with a Pen directly. A Pen is used under the covers, but you set all of the properties indirectly. Table 7-1 shows how Shape properties correspond to Pen properties.

You will typically deal directly with a Pen only if you work at a lower level, such as with the GeometryDrawing in a DrawingBrush.


The line width is set with the Thickness property. For simple outlines, this and Brush may be the only properties you set. However, Pen has more to offer. For example, you can set a dash pattern with the DashArray property. This is simply an array of numbers. Each number corresponds to the length of a particular segment in the dash pattern. Example 7-42 illustrates the simplest possible pattern.

Example 7-42. DashArray
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="1" />

This indicates that the first segment in the dash pattern is of length one. The dash pattern repeats, and since only one segment length has been specified, every segment will be of length 1. Figure 7-49 shows the result.

Figure 7-49. Simple dash pattern


Example 7-43 shows two slightly more interesting pattern sequences. Note that the second case supplies an odd number of segments. This means that the first time around, the solid segments will be of size 6 and the gap will be of size 1, but when the sequence repeats, the solid segment will be of length 1 and the gaps of size 6. So, the effective length of the dash pattern is doubled. The results of both patterns are shown in Figure 7-50.

Example 7-43. Dash patterns
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="10 1 3 1" />
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="6 1 6" />

Figure 7-50. Longer dash patterns


Corners can be drawn in three different ways. The LineJoin property can be set to Miter, Bevel, or Round, shown from left to right in Figure 7-51.

Figure 7-51. LineJoin types: Miter, Bevel, and Round


For open shapes such as Line or PolyLine, you can specify the shape of the starts and ends of lines with the StartLineCap and EndLineCap properties. The DashCap property specifies the shape that dashes start and end with. These properties support four styles of cap: Round, triangle, Flat, and Square, shown from top to bottom in Figure 7-52. Flat and Square both square off the ends of lines. The distinction is that with Flat, the flat end intersects the end point of the line, but with Square, it extends beyond it. The amount by which it overshoots the line is equal to half the line thickness.

Figure 7-52. Line cap styles: Round, Triangle, Flat, and Square



©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