XAML Features


XAML is a form of XML that defines certain allowed combinations of XML elements. For example, a XAML file should have a single root element that represents a Window. That object can have a single child element that is normally a container. The container can hold several children with specifically defined properties such as Width and Height.

XAML is a very complicated language, and many of its features are available only in certain places within the file. For example, inside a Button element you can place attributes such as Background, BorderThickness, Margin, Width, Height, and Content. The XAML text editor provides IntelliSense that makes figuring out what is allowed in different places easier, but building a XAML file can still be quite challenging.

Tip 

One good way to learn XAML is to go online and search for examples. The Microsoft web site has lots of examples, as do several other web sites. Although the documentation isn’t always easy to use, the examples can help you learn specific techniques. Some good places to start include the XAML overview at msdn2.microsoft.com/library/ms752059.aspx and the Windows Presentation Foundation development page at msdn2.microsoft.com/library/ms754130.aspx.

The following sections describe some of the basic building blocks of a XAML application. They explain how to build objects; how to use resources, styles, and templates to make objects consistent and easier to modify; and how to use transformations and animations to make objects interactive. The section “Procedural WPF” later in this chapter explains how to do these things in Visual Basic code instead of XAML.

Objects

WPF objects are represented by XML elements in the XAML file. Their properties are represented either by attributes within the base elements or as separate elements within the main element.

For example, the following XAML code shows a Window containing a Grid object. The Grid element contains a Background attribute that makes the object’s background red.

  <Window x:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     Title="WindowsApplication1" Height="235" Width="300"     >     <Grid Background="#FFFF0000">     </Grid> </Window> 

More complicated properties must be set in their own sub-elements. The following code shows a similar Grid that has a linear gradient background:

  <Window x:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     Title="WindowsApplication1" Height="235" Width="300"     >     <Grid >       <Grid.Background>         <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">           <GradientStop Color="Red" Offset="0.0" />           <GradientStop Color="White" Offset="0.5" />           <GradientStop Color="Blue" Offset="1.0" />         </LinearGradientBrush>       </Grid.Background>     </Grid> </Window> 

Instead of using a Background attribute, the Grid element contains a Grid.Background element. That, in turn, contains a LinearGradientBrush element that defines the background. The StartPoint and EndPoint attributes indicate that the gradient should start at the upper-left corner of the grid (0, 0) and end at the lower right (1, 1). The GradientStop elements inside the brush’s definition set the colors that the brush should display at different fractions of the way through the gradient. In this example, the gradient starts red, changes to white halfway through, and changes to blue at the end.

Tip 

Note that you cannot define an object’s Background property more than once. If you include a Background attribute and a Grid.Background element for the same grid, the XAML editor complains.

Object elements often contain other elements that further define the object. The following code defines a grid that has two rows and three columns. (From now on I’m leaving out the Window element to save space.) The rows each occupy 50 percent of the grid’s height (the * means percent). The first column is 50 pixels wide and the other two columns each take up 50 percent of the remaining width.

  <Grid >   <Grid.RowDefinitions>     <RowDefinition Width="0.5*" />     <RowDefinition Width="0.5*" />   </Grid.RowDefinitions>   <Grid.ColumnDefinitions>     <ColumnDefinition Width="50" />     <ColumnDefinition Width="0.5*" />     <ColumnDefinition Width="0.5*" />   </Grid.ColumnDefinitions> </Grid> 

An object element can also contain content for the object. In some cases, the content is simple text. The following example defines a Button object that has the caption Click Me:

  <Button Margin="2,2,2,2" Name="btnClickMe">Click Me</Button> 

An object’s content may also contain other objects. The following code defines a grid with three rows and three columns holding nine buttons:

  <Grid >   <Grid.RowDefinitions>     <RowDefinition Width="0.33*" />     <RowDefinition Width="0.33*" />     <RowDefinition Width="0.33*" />   </Grid.RowDefinitions>   <Grid.ColumnDefinitions>     <ColumnDefinition Width="0.33*" />     <ColumnDefinition Width="0.33*" />     <ColumnDefinition Width="0.33*" />   </Grid.ColumnDefinitions>   <Button Grid.Row="0" Grid.Column="0" Margin="5,5,5,5" Name="btn00">0,0</Button>   <Button Grid.Row="0" Grid.Column="1" Margin="5,5,5,5" Name="btn01">0,1</Button>   <Button Grid.Row="0" Grid.Column="2" Margin="5,5,5,5" Name="btn02">0,2</Button>   <Button Grid.Row="1" Grid.Column="0" Margin="5,5,5,5" Name="btn10">1,0</Button>   <Button Grid.Row="1" Grid.Column="1" Margin="5,5,5,5" Name="btn11">1,1</Button>   <Button Grid.Row="1" Grid.Column="2" Margin="5,5,5,5" Name="btn12">1,2</Button>   <Button Grid.Row="2" Grid.Column="0" Margin="5,5,5,5" Name="btn20">2,0</Button>   <Button Grid.Row="2" Grid.Column="1" Margin="5,5,5,5" Name="btn21">2,1</Button>   <Button Grid.Row="2" Grid.Column="2" Margin="5,5,5,5" Name="btn22">2,2</Button> </Grid> 

Usually, it is easiest to start building a Window by using the graphical XAML editor, but you may eventually want to look at the XAML code to see what the editor has done. It often produces almost what you want. For example, it is common for the editor to use long double-precision values instead of integers as in setting a Margin value to 5,5,5.00000000000001,5.00000000000001.

It can also be hard to make controls have exactly the same size and align them perfectly. You can fix some of these values in the Properties window, but sometimes it’s just easier to edit the XAML code directly.

Resources

Consider the calculator application shown in Figure 26-7. This program contains three groups of buttons that use radial gradient backgrounds with similar colors. The number buttons, +/-, and the decimal point have yellow backgrounds. The CE, C, and = buttons have blue backgrounds, and the operator buttons have green backgrounds.

image from book
Figure 26-7: This program uses resources to simplify maintenance.

You could build each button separately, including the appropriate RadialGradientBrush objects to give each button the correct background. Suppose, however, you decide to change the color of all of the number buttons from yellow to red. You would have to edit each of their 12 RadialGradientBrush objects to give them their new colors. In addition to being a lot of work, those changes would give you plenty of chances to make mistakes. The changes would be even harder if you decide to change the numbers of colors (perhaps having the brush shade from yellow to red to orange), or if you want to use a completely different brush for the buttons such as a LinearGradientBrush.

One of the ways XAML makes maintaining projects easier is by letting you define resources. You can then use the resources when defining the objects. In this example, you can define a resource to represent button backgrounds, and then use that resource to set each button’s Background property. If you later need to change the background, you only need to update the resource.

The following code shows how the calculator application shown in Figure 26-7 creates a LinearGradient?Brush resource called brResult. Ellipses show where code has been omitted to make it easier to read.

  <Window x:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     Title="XamlCalculator" Height="292" Width="227" Focusable="True">   <Window.Resources>    ...     <LinearGradientBrush x:Key="brResult"       StartPoint="0,0"       EndPoint="1,1">       <GradientStop Color="LightBlue" Offset="0.0" />       <GradientStop Color="AliceBlue" Offset="1.0" />     </LinearGradientBrush>     ...   </Window.Resources>    ... </Window> 

The Window element contains a Window.Resources tag that contains the resource definitions. The LinearGradientBrush element defines the brush. One of the more important pieces of this element is the x:Key attribute, which identifies the brush for later use.

The following code shows how the calculator program defines the Label that displays calculation results. The Background attribute refers to the resource brNumber.

  <Label Name="lblResult"   Background="{StaticResource brResult}"   Grid.ColumnSpan="4"   Margin="2,2,2,2"   HorizontalContentAlignment="Right"   VerticalContentAlignment="Center">0</Label> 

Styles

Resources make it easy to create many controls that share an attribute such as a background. Styles take attributes a step further by allowing you to bundle multiple attributes into one package. For example, you could define a style that includes background, width, height, and font properties. Then you could use the style to help define controls.

You can also use styles to define other styles. For example, you can make a base style to be applied to every button in an application. Then you can derive other styles for different kinds of buttons from the base style.

The following example defines a style named styAllButtons. It contains Setter elements that set controls’ properties. This style sets a control’s Focusable property to false and its Margin property to 2,2,2,2.

  <Style x:Key="styAllButtons">   <Setter Property="Control.Focusable" Value="false" />   <Setter Property="Control.Margin" Value="2,2,2,2" />   </Style> 

The following code defines a style named styClear for the calculator’s C, CE, and = buttons. This style’s name is based on the style styAllButtons.

  <Style x:Key="styClear" BasedOn="{StaticResource styAllButtons}">   <Setter Property="Control.Background" Value="{StaticResource brClear}" />   <Setter Property="Grid.Row" Value="1" />   <Setter Property="Control.Margin" Value="2,20,2,2" /> </Style> 

The BasedOn attribute makes the new style start with the properties defined by styAllButtons. The new style then uses two Setter elements to add new values for the Background and Grid.Row properties (these buttons are all in row 1 in the calculator). It then overrides the styAllButtons style’s value for the Margin property.

The following code shows how the program defines its C button. By setting the button’s style to styClear, the code sets most of the button’s properties with a single statement. It then sets the button’s Grid.Column property (those values are different for the C, CE, and = buttons) and its content.

  <Button Name="btnC"   Style="{StaticResource styClear}"   Grid.Column="1">C</Button> 

Styles let the program keep all of the common properties for a set of controls in a single location. As the previous code shows, it also keeps the controls’ definitions very simple.

Styles also let you easily change the controls’ properties later. For example, if you later decide to specify the font family and font size for the calculator’s C, CE, and = buttons, you only need to add the appropriate Setter elements to styClear instead of adding a new property to every button. If you want to set the font for every button in the program, you simply add the appropriate Setter elements to styAllButtons and the other styles automatically pick up the changes.

Templates

Templates determine how controls are drawn and how they behave by default. For example, the default button template makes buttons turn light blue when the mouse hovers over them. When you press the button, it grows slightly darker and shows a thin shadow along its upper and left edges. By using Template elements, you can override these default behaviors.

The following code contained in the Window.Resources section defines a button template:

  <Style TargetType="Button">   <Setter Property="Margin" Value="2,2,2,2" />   <Setter Property="Template">     <Setter.Value>       <ControlTemplate TargetType="{x:Type Button}">         <Grid>           <Polygon x:Name="pgnBorder"             Stroke="Purple"              StrokeThickness="5"              Points="0.2,0 0.8,0 1,0.2 1,0.8 0.8,1 0.2,1 0,0.8 0,0.2"             Stretch="Fill"             Fill="{StaticResource brOctagonUp}">           </Polygon>           <ContentPresenter              HorizontalAlignment="Center"             VerticalAlignment="Center" />         </Grid>         <!-   Triggers ->         <ControlTemplate.Triggers>           <Trigger Property="IsMouseOver" Value="true">             <Setter TargetName="pgnBorder" Property="Stroke" Value="Black" />             <Setter TargetName="pgnBorder" Property="Fill"               Value="{StaticResource brOctagonOver}" />           </Trigger>         </ControlTemplate.Triggers>       </ControlTemplate>     </Setter.Value>   </Setter> </Style> 

The code begins with a Style element that contains two Setter elements. The first Setter sets a button’s Margin property to 2,2,2,2. The second Setter sets a Template property. The Setter’s value is a ControlTemplate element targeted at Buttons.

The ControlTemplate contains a Grid that it uses to hold other elements. In this example, the Grid holds a Polygon element named pgnBorder. The Points attribute lists the points used to draw the polygon. Because the polygon’s Fill attribute is set to Stretch, the polygon is stretched to fill its parent area, and Points coordinates are on a 0.0 to 1.0 scale within this area. The polygon’s Fill attribute is set to the brOctagonUp brush defined elsewhere in the Window.Resources section and not shown here. This is a RadialGradientBrush that shades from white in the center to red at the edges.

The ControlTemplate element also contains a Triggers section. The single Trigger element in this section executes when the button’s IsMouseOver condition is true. When that happens, a Setter changes the pgnBorder polygon’s Stroke property to Black. A second Setter sets the polygon’s Fill property to another brush named brOctagonOver. This brush (which isn’t shown here) shades from red in the center to white at the edges.

Because this style does not have an x:Key attribute, it applies to any button in the Window that doesn’t have a Style set explicitly.

The following code shows how the ButtonTemplate example program creates its controls:

  <Grid>   <Grid.ColumnDefinitions>     <ColumnDefinition Width="0.25*" />     <ColumnDefinition Width="0.25*" />     <ColumnDefinition Width="0.25*" />     <ColumnDefinition Width="0.25*" />   </Grid.ColumnDefinitions>   <Grid.RowDefinitions>     <RowDefinition Height="0.50*" />     <RowDefinition Height="0.50*" />   </Grid.RowDefinitions>   <Button Name="btnOne" Content="One" Grid.Row="1" Grid.Column="0" />   <Button Name="btnTwo" Content="Two" Grid.Row="1" Grid.Column="1" />   <Button Name="btnThree" Content="Three" Grid.Row="1" Grid.Column="2" />   <Button Name="btnFour" Content="Four" Grid.Row="1" Grid.Column="3" />   <Button Name="btnClickMe" Content="Click Me"     Style="{StaticResource styYellowButton}" />   <Button Name="btnYellow" Content="I'm Yellow"     Style="{StaticResource styYellowButton}"     Grid.Column="2" Grid.Row="0" /> </Grid> 

The Window contains a Grid that holds six buttons. The first four buttons do not explicitly set their Style, so they use the styOctagon style.

The final buttons set their Style attributes to styYellowButton (also defined in the Windows.Resources section, but not shown here) so they display a yellow background. When you hover the mouse over them, they switch to an orange background. If you press the mouse down on these buttons, they change to a red background with white text that says “Pushed!” Download the complete example to see how those triggers work.

Figure 26-8 shows the result. The mouse is hovering over the second button, so it displays the black border and its background shades from red in the center to white at the edges.

image from book
Figure 26-8: Templates let you change the appearance and behavior of objects such as buttons.

Tip 

In the version of Visual Studio I’m currently using, the octagonal buttons don’t draw in the graphical XAML editor. At runtime, they look and work as intended, but in the designer, they only display their text.

You can use templates to change the appearance and behavior of XAML objects to give your applications distinctive appearances, but you probably shouldn’t get too carried away. Although you can make buttons radically change their colors, shapes, captions, and other characteristics when the user interacts with them, doing so may be very distracting. Use templates to make your applications distinctive, but not overwhelming.

Transformations

Properties determine a control’s basic appearance, but you can further modify that appearance by using a RenderTransform element. The following code creates a button that has been rotated 270 degrees. The Button.RenderTransform element contains a RotateTransform element that represents the rotation.

  <Button Name="btnSideways"   Content="Sideways"   Background="{StaticResource brButton}"   Margin="-6,-6.5,0,0"   Height="43"   HorizontalAlignment="Left"   VerticalAlignment="Top"   Width="94">   <Button.RenderTransform>     <RotateTransform Angle="270" CenterX="75" CenterY="50" />   </Button.RenderTransform> </Button>  

XAML also provides TranslateTransform and ScaleTransform elements that let you translate and scale an object. Figure 26-9 shows several buttons that have been rotated and scaled vertically and horizontally.

image from book
Figure 26-9: Buttons can be rotated and scaled vertically and horizontally by using RotateTransform and ScaleTransform.

XAML also defines a TransformGroup element that you can use to perform a series of transformations on an object. For example, a TransformGroup would let you translate, scale, rotate, and then translate an object again.

Animations

The section “Templates” earlier in this chapter shows how to use Triggers to make an object change its appearance in response to events. For example, it shows how to make a button change its background and border color when the mouse moves over it.

XAML also provides methods for scripting more complicated actions that take place over a defined period of time. For example, you can make a button spin slowly for two seconds when the user clicks it.

You use a trigger to start the animation object to start the animation and a Storyboard object to control it. The SpinButton example program uses the following code to make a button rotate around its center when it is clicked:

  <Button Name="btnSpinMe" Content="Spin Me"    Width="150" Height="100">   <Button.Background>     <RadialGradientBrush        Center="0.5,0.5"       RadiusX="1.0" RadiusY="1.0">       <GradientStop Color="Yellow" Offset="0.0" />       <GradientStop Color="Orange" Offset="1.0" />     </RadialGradientBrush>   </Button.Background>   <Button.RenderTransform>     <RotateTransform x:Name="rotButton"       Angle="0"        CenterX="75" CenterY="50" />   </Button.RenderTransform>   <Button.Triggers>     <EventTrigger RoutedEvent="Button.Click">       <EventTrigger.Actions>         <BeginStoryboard>           <Storyboard              Duration="1"              Storyboard.TargetName="rotButton"              Storyboard.TargetProperty="(RotateTransform.Angle)">             <DoubleAnimationUsingKeyFrames>               <SplineDoubleKeyFrame KeyTime="0:0:00.0" Value="0.0" />               <SplineDoubleKeyFrame KeyTime="0:0:00.2" Value="30.0" />               <SplineDoubleKeyFrame KeyTime="0:0:00.8" Value="330.0" />               <SplineDoubleKeyFrame KeyTime="0:0:01.0" Value="360.0" />             </DoubleAnimationUsingKeyFrames>           </Storyboard>         </BeginStoryboard>       </EventTrigger.Actions>     </EventTrigger>           </Button.Triggers> </Button> 

Much of this code should seem familiar by now. The Button element’s attributes set its name, contents, and size. A Background element fills the button with a RadialGradientBrush.

The Button element contains a RenderTransform element similar to the ones described in the previous section. In this case, the transform is a RotateTransform with angle of rotation initially set to 0 so that the button appears in its normal orientation. Its center is set to the middle of the button. The transform is named rotButton so that other code can refer to it later.

After the transform element, the code contains a Triggers section. This section holds an EventTrigger element that responds to the Button.Click routed event. A routed event is simply an event that is processed so that you can catch it with a normal Visual Basic event handler. When the user clicks the button, the Button.Click event fires and this trigger springs into action.

The trigger’s Actions element contains the things that the trigger should perform. In this example, the trigger performs the BeginStoryboard action. Inside the BeginStoryboard element is a Storyboard element that represents the things the storyboard should do.

The Storyboard element’s Duration attribute indicates that the storyboard should perform its actions in 1 second. The TargetName attribute gives the target object on which the storyboard should act, in this case the RotateTransform object named rotButton. The TargetProperty attribute tells what property of the target button the storyboard should manipulate (in this example, the object’s Angle property).

The Storyboard element contains a DoubleAnimationUsingKeyFrames element. A key frame is specific point in an animation sequence with known values. The program calculates values between the key frame values to make the animation smooth.

This DoubleAnimationUsingKeyFrames element holds a collection of SplineDoubleKeyFrame elements that define the animation’s key values. Each key frame gives its time in the animation in hours, minutes, and seconds, and the value that the controlled property should have at that point in the animation. In this example, the rotation transformation’s angle should have value 0 when the storyboard starts, a value of 30 when the animation is 20 percent complete, a value of 330 when the storyboard is 80 percent complete, and a value of 360 when the storyboard finishes. The result is that the button rotates slowly for the first 0.2 seconds, spins relatively quickly for the next 0.6 seconds, and then finishes rotating at a more leisurely pace.

Example program SpinButton animates a single property, the button’s angle of rotation, but you can animate more than one property at the same time if you must. Program SpinAndGrowButton simultaneously animates a button’s angle of rotation and size. This example has two key differences from program SpinButton.

First, the new button’s RenderTransform element contains a TransformGroup that contains two transformations, one that determines the button’s angle of rotation and one that determines its scaling.

  <Button.RenderTransform>   <TransformGroup>     <RotateTransform x:Name="rotButton"       Angle="0"        CenterX="50" CenterY="25" />     <ScaleTransform x:Name="scaButton"       ScaleX="1" ScaleY="1"       CenterX="50" CenterY="25" />   </TransformGroup> </Button.RenderTransform> 

The second difference is in the new button’s Storyboard. The following code omits the animation’s TargetName and TargetProperty from the Storyboard element’s attributes. It includes three DoubleAnimationUsingKeyFrame elements inside the Storyboard, and it is there that it sets the TargetName and TargetProperty. The three animations update the button’s angle of rotation, horizontal scale, and vertical scale.

  <Storyboard Duration="1">   <!-   Rotate ->   <DoubleAnimationUsingKeyFrames     Storyboard.TargetName="rotButton"      Storyboard.TargetProperty="(RotateTransform.Angle)">     <SplineDoubleKeyFrame KeyTime="0:0:00.0" Value="0.0" />     <SplineDoubleKeyFrame KeyTime="0:0:01.0" Value="360.0" />   </DoubleAnimationUsingKeyFrames>   <!-   ScaleX ->   <DoubleAnimationUsingKeyFrames     Storyboard.TargetName="scaButton"     Storyboard.TargetProperty="(ScaleTransform.ScaleX)">     <SplineDoubleKeyFrame KeyTime="0:0:00.0" Value="1.0" />     <SplineDoubleKeyFrame KeyTime="0:0:00.5" Value="2.0" />     <SplineDoubleKeyFrame KeyTime="0:0:01.0" Value="1.0" />   </DoubleAnimationUsingKeyFrames>   <!-   ScaleY ->   <DoubleAnimationUsingKeyFrames     Storyboard.TargetName="scaButton"     Storyboard.TargetProperty="(ScaleTransform.ScaleY)">     <SplineDoubleKeyFrame KeyTime="0:0:00.0" Value="1.0" />     <SplineDoubleKeyFrame KeyTime="0:0:00.5" Value="2.0" />     <SplineDoubleKeyFrame KeyTime="0:0:01.0" Value="1.0" />   </DoubleAnimationUsingKeyFrames> </Storyboard> 

By using XAML Storyboards, you can build complex animations that run when certain events occur. As with templates, however, you should use some restraint when building storyboard animations. A few small animations can make an application more interesting, but too many large animations can distract and annoy the user.

Drawing Objects

WPF provides several objects for drawing two-dimensional shapes. The following sections summarize the most useful of these objects. Refer to Figure 26-10 for examples of the basic shape commands. The top row of shapes from left to right demonstrate a filled polygon, a polyline drawn with a dashed pen, and an ellipse filled with a gradient brush. The shapes in the bottom row are two lines with round end caps, a path that contains two lines and a curve, and an elliptical arc.

image from book
Figure 26-10: WPF includes objects to draw shapes such as lines, polygons, and ellipses.

Line

The Line object draws a straight line between two points. The X1, Y1, X2, and Y2 attributes determine the line’s endpoints. The following code drew the X in the lower-left corner of Figure 26-10:

  <Line X1="10" Y1="10" X2="90" Y2="90"   Grid.Column="0" Grid.Row="1"   Stroke="Blue" StrokeThickness="10"   StrokeStartLineCap="Round" StrokeEndLineCap="Round" /> <Line X1="90" Y1="10" X2="10" Y2="90"   Grid.Column="0" Grid.Row="1"   Stroke="Blue" StrokeThickness="10"   StrokeStartLineCap="Round" StrokeEndLineCap="Round" /> 

The Stroke and StrokeThickness attributes determine the lines’ color and thickness.

The StrokeStartLineCap and StrokeEndLineCap attributes determine the appearance of the lines’ start and endpoints. This example draws rounded end caps.

Ellipse

The Ellipse object draws an ellipse. The following code draws the ellipse filled with a LinearGradientBrush shown in the upper right in Figure 26-10:

  <Ellipse Margin="2,20,2,20"   Grid.Column="2" Grid.Row="0"   Stroke="Orange" StrokeThickness="5">   <Ellipse.Fill>     <LinearGradientBrush        StartPoint="0,0"       EndPoint="1,0">       <GradientStop Color="Green" Offset="0.0" />       <GradientStop Color="White" Offset="0.5" />       <GradientStop Color="Green" Offset="1.0" />     </LinearGradientBrush>   </Ellipse.Fill> </Ellipse> 

The following code draws an ellipse that is 50 pixels wide and 80 pixels tall. The HorizontalAlignment and VerticalAlignment attributes move the ellipse into the grid cell’s upper-left corner. Then the Margin attribute moves the upper-left corner of the ellipse’s bounding rectangle to position (10, 10).

  <Ellipse Width="50" Height="80"   Grid.Column="2" Grid.Row="0"   HorizontalAlignment="Left"   VerticalAlignment="Left"   Margin="10,10,0,0"   Stroke="Green" StrokeThickness="5" /> 

Rectangle

The Rectangle object draws a rectangle. The syntax is similar to that used to draw ellipses.

Polygon

The Polygon object draws a closed polygon. Its Points attribute lists the points that should be connected. The Polygon object automatically closes its figure by connecting the final point to the first point.

The following code draws the four-pointed star in the upper left in Figure 26-10:

  <Polygon Margin="0,0,0,0"   Grid.Column="0" Grid.Row="0"   Points="10,10 50,40 90,10 60,50 90,90 50,60 10,90 40,50"   Fill="LightBlue"   Stroke="Red" StrokeThickness="3" /> 

Polyline

The Polyline object is similar to the Polygon object, except that it does not automatically close the drawing by connecting the final point to the first point.

The following code draws the dashed lines shown in the upper middle in Figure 26-10:

  <Polyline Margin="0,0,0,0"   Grid.Column="1" Grid.Row="0"   Points="20,20 40,40 60,30 90,90 30,70"   Stroke="Black" StrokeLineJoin="Round"   StrokeThickness="3"   StrokeDashArray="2,1,2,3" />   <Ellipse Margin="2,20,2,20"     Grid.Column="2" Grid.Row="0"     Stroke="Orange" StrokeThickness="5">     <Ellipse.Fill>       <LinearGradientBrush          StartPoint="0,0"       EndPoint="1,0">       <GradientStop Color="Green" Offset="0.0" />       <GradientStop Color="White" Offset="0.5" />       <GradientStop Color="Green" Offset="1.0" />     </LinearGradientBrush>   </Ellipse.Fill> </Ellipse> 

This code demonstrates a few additional features of line drawing in general. The StrokeLineJoin attribute determines how lines are connected. In this example, the lines are joined with rounded corners.

The StrokeDashArray attribute determines the lines’ dash pattern. The numbers indicate the number of units the line draws and skips. In this example, the value 2,1,2,3 means the line draws 2 units, skips 1 unit, draws 2 units, and skips 3 units. Each unit represents the line’s width.

Path

The Path object draws a series of shapes such as lines, arcs, and curves. A Path object can be incredibly complex, and can include any of the other drawing objects plus a few others that draw smooth curves.

You can define a Path object in two ways. First, you can make the Path element contain other elements that define objects drawn by the path.

The second (and more concise) method is to use the Path element’s Data attribute. This is a text attribute that contains a series of coded commands for drawing shapes. For example, the following code makes the Path move to the point (20, 20), and then draw to connect the following points (80, 20), (50, 60), (90, 100), and (50, 120):

  <Path Stroke="Gray" StrokeThickness="5" Grid.Column="1" Grid.Row="1"   Data="M 20,20 L 80,20 50,60 90,100 50,120" /> 

You can use spaces or commas to separate point coordinates. To make it easier to read the code, you may want to use commas between points’ X and Y coordinates and spaces between points, as in the previous example.

Some commands allow both uppercase and lowercase command letters. For those commands, the lowercase version means that the following points’ coordinates are relative to the previous points’ coordinates. For example, the following data makes the object move to the point (10, 20) and then draws to the absolute coordinates (30, 40):

  Data="M 10,20 L 30,40" 

In contrast, the following data moves to the point (10, 20) as before, but then moves distance (30, 40) relative to the current position. The result is that the line ends at point (10 + 30, 20 + 40) = (40, 60).

  Data="M 10,20 l 30,40" 

There isn’t enough room for a complete discussion of the Path object, but the following table summarizes the commands that you can include in the Data attribute.

Open table as spreadsheet

Command

Result

Example

F0

Sets the fill rule to the odd/even rule.

F0

F1

Sets the fill rule to the non-zero rule.

F1

M or m

Moves to the following point without drawing.

M 10,10

L or l

Draws a line to the following point(s).

L 10,10 20,20 30,10

H or h

Draws a horizontal line to from the current point to the given X coordinate.

h 50

V or v

Draws a vertical line from the current point to the given Y coordinate.

v 30

C or c

Draws a cubic Bezier curve. This command takes three points as parameters: two control points and an endpoint. The curve starts at the current point moving toward the first control point. It ends at the endpoint, coming from the direction of the second control point.

C 20,20 60,0 50,50

S or s

Draws a smooth cubic Bezier curve. This command takes two points as parameters: a control point and an endpoint. The curve defines an initial control point by reflecting the second control point used by the previous S command, and then uses it plus its two points to draw a cubic Bezier curve. This makes a series of Bezier curves join smoothly.

S 60,0 50,50 S 80,60 50,70

Q or q

Draws a quadratic Bezier curve. This command . takes two points as parameters: a control point and an endpoint. The curve starts at the current point moving toward the control point. It ends at the endpoint, coming from the direction of the control point.

Q 80,20 50,60

T or t

Draws a smooth cubic Bezier curve. This command takes one point as a parameter: an endpoint. The curve defines a control point by reflecting the control point used by the previous T command, and then uses it to draw a quadratic Bezier curve. The result is a smooth curve that passes through each of the points given as parameters to successive T commands.

T 80,20 T 50,60 T 90,100

A or a

Draws an elliptical arc. This command takes five parameters:

  • size - The X and Y radii of the arc

  • rotation_angle - The ellipse’s angle of rotation

  • large_angle - 0 if the arc should span less than 180; 1 if the arc should span 180 degrees or more

  • sweep_direction - 0 if the arc should sweep counterclockwise; 1 if it should sweep clockwise

  • end_point - The point where the arc should end

A 50,20 0 1 0 60,80

Z or z

Closes the figure by drawing a line from the current point to the Path’s starting point.

Z

In Figure 26-10, the shape in the bottom center is a Path drawn by the following code:

  <Path Margin="0,0,0,0"   Grid.Column="1" Grid.Row="1"   Data="M 10,10 L 90,20 l -30,30 c 50,50 -50,50 -30,-20"   Stroke="Green" StrokeThickness="3" /> 

The Path object’s Data attribute moves to the point (10, 10), draws a line to the absolute position (90, 20), and draws to the relative position (-30, 30) ending at the point (90 – 30, 20 + 30) = (60, 50).

It finishes by drawing a relative cubic Bezier curve starting at the current point (60, 50). The curve uses relative coordinates, so all of the curve’s points are relative to the current point (60, 50). The first control point is at (60 + 50, 50 + 50) = (110, 100). The second control point is at (60 – 50, 50 + 50) = (10, 100). The endpoint is at (60 – 30, 50 – 20) = (30, 30). Keep in mind that all of these coordinates are relative to the Grid cell that contains the Path object.

The elliptical arc in the bottom right of Figure 26-10 is drawn by the following code:

 <Path Stroke="Blue" StrokeThickness="3"   Grid.Row="1" Grid.Column="2"   Data="M 20,20 A 30,20 30 1 0 60,80" />

The Data attribute moves to point (20, 20). The arc command draws an arc with X radius 30 and Y radius 20. The ellipse that contains the arc is rotated 30 degrees. The next two parameters are flags that indicate that the arc spans a large angle, and that it should sweep counterclockwise from the start point (20, 20). The final parameter is the endpoint (60, 80).

Figure 26-11 shows examples of the four different kinds of Bezier curves. This program also draws a gray polyline to show the curves’ parameters.

image from book
Figure 26-11: The Path object can draw Bezier curves.

The cubic Bezier curve on the left connects the two endpoints using the two middle points to determine the curve’s direction at the endpoints.

The smooth cubic Bezier curve shown next passes through the first, third, and fifth points. The second point determines the curve’s direction as it leaves the first point and as it enters the third point. The curve automatically defines a control point to determine the direction leaving the third point, so the curve passes through the point smoothly. Finally, the fourth point determines the curve’s direction as it ends at the fifth point.

The next curve shows two quadratic Bezier curves. The first curve connects the first and third points with the second point determining the curve’s direction at both points. The second curve connects the third and fifth points, using the fourth to determine its direction.

The final curve in Figure 26-11 uses an M command to move to the point (20, 20). It then uses three smooth quadratic Bezier curves to connect the following three points smoothly. The curve automatically defines the control points it needs to connect the points smoothly.

With all of these drawing objects at your disposal, particularly the powerful Path object, you can draw just about anything you need. The graphical XAML editor does not provide interactive tools for drawing shapes, but you can draw them by using the XAML text editor. It may help to sketch out what you want to draw on graph paper first.




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net