Chapter 12. Using WPF Controls

The code behind WPF controls is the same as the code behind Windows Forms controls. That means that everything the earlier chapters have explained about applications, forms, controls, Visual Basic code, error handling, drawing, printing, reports, and so forth, still work almost exactly as before.

Chapter 11, "Selecting WPF Controls," briefly describes the most common WPF controls, grouped by category to help you pick the control that best suits a particular task. This chapter provides more detail about WPF. It explains some of the more important concepts that underlie WPF. It also gives more detail about how particular controls work and tells how you can use them in your applications.

WPF is a huge topic. It basically reproduces all of the functionality of Windows Forms programming, and then some. This chapter cannot hope to cover all of the concepts, tools, and techniques used by WPF. Instead, it introduces some of the more important concepts and explains how to build basic WPF forms.

WPF CONCEPTS

WPF applications are similar in concept to Windows Forms applications in many respects. Both display a form or window that contains controls. Controls in both systems provide properties, methods, and events that determine the control's appearance and behavior.

Windows Forms applications use a set of controls provided by the System.Windows.Forms namespace. WPF applications use a different set of controls in the System.Windows.Controls namespace. Many of these controls serve similar functions to those used by Windows Forms applications, but they provide a different set of capabilities. For example, both namespaces have buttons, labels, combo boxes, and check boxes, but their appearances and abilities are different.

WPF uses these similar, but different, controls for two main reasons:

  • To take better advantage of the graphics capabilities of modern computer hardware and software. The new controls can more easily provide graphical effects such as transparent or translucent backgrounds, gradient shading, rotation, two- and three-dimensional appearance, multimedia, and other effects.

  • To provide a greater separation between the user interface and the code behind it. The following sections describe this idea and some of the other key WPF concepts in greater detail.

Separation of User Interface and Code

The idea of separating the user interface from the code isn't new. Visual Basic developers have been building thin user interface applications for years. Here, the user interface contains as little code as possible, and calls routines written in libraries to do most of the work.

Unfortunately, the code that calls those libraries sits inside the same file that defines the user interface, at least in Windows Forms applications. That means you cannot completely separate the code from the user interface. For example, if one developer wants to modify the user interface, another developer cannot simultaneously modify the code behind it.

WPF separates the user interface from the code more completely. The program stores the user interface definition in a XAML file.

Associated with a XAML file is a code file containing Visual Basic code. It contains any code you write to respond to events and manipulate the controls much as Windows Forms code can. Unlike the case with Windows Forms, WPF keeps the user interface definition and the code behind it in two separate files so, in theory at least, different developers can work on the user interface and the code at the same time. For example, a graphics designer can use the Expression Blend design tool to build the user interface, defining the forms' labels, menus, buttons, and other controls. Then a Visual Basic developer can attach code to handle the controls' events.

Note

Though it isn't a free product, Expression Blend provides some useful tools that are missing from Visual Studio such as tools to record animations. If you frequently need to build property animations, I highly recommend that you give it a try.

You can learn more about Expression Blend and download a trial version at www.microsoft.com/expression/products/Overview.aspx?key=blend.

Because the user interface definition is separate from the code behind it, the graphic designer can later edit the XAML to rearrange controls, change their appearance, and otherwise modify the user interface while the code behind it should still work unchanged.

WPF Control Hierarchies

In a WPF application, the Window class plays a role similar to the one played by a Form in a Windows Forms application. Whereas a Form can contain any number of controls, a Window can contain only one. If you want a WPF form to display more than one control, you must first give it some kind of container control, and then place other controls inside that one.

For example, when you create a WPF application, its Window initially contains a Grid control that can hold any number of other controls, optionally arranged in rows and columns. Other container controls include Canvas, DockPanel, DocumentViewer, Frame, StackPanel, and TabControl.

The result is a tree-like control hierarchy with a single Window object serving as the root element. This matches the hierarchical nature of XAML. Because XAML is a form of XML, and XML files must have a single root element, XAML files must also have a single root element. When you look at XAML files later in this chapter, you will find that they begin with a Window element that contains all other elements.

Many non-container controls can hold only a single element, and that element is determined by the control's Content property. For example, you can set a Button control's Content property to the text that you want to display.

A control's Content property can only have a single value, but that value does not need to be something simple such as text. For example, Figure 12-1 shows a Button containing a Grid control that holds three labels.

This Button contains a Grid that holds three labels.

Figure 12.1. This Button contains a Grid that holds three labels.

WPF IN THE IDE

The Visual Studio IDE includes editors for manipulating WPF Window classes and controls. Although many of the details are different, the basic operation of the IDE is the same whether you are building a Windows Forms application or a WPF application. For example, you can use the WPF Window Designer to edit a WPF window. You can select controls from the Toolbox and place them on the window much as you place controls on a Windows Form.

Despite their broad similarities, the Windows Forms Designer and the WPF Window Designer differ in detail. Although the Properties window displays properties for WPF controls much as it does for Windows Forms controls, many of the property values are not displayed in similar ways.

The window represents many Boolean properties with check boxes. It represents other properties that take enumerated values with combo boxes where you can select a value or type one in (if you know the allowed values). The window represents some object properties with the objects' type names and doesn't allow you to select objects as the Properties window does in the Windows Forms Designer.

Future Visual Studio releases may make Expression Blend more consistent with Visual Studio, although some more advanced features (such as animation recording) are likely to remain only in Expression Blend to encourage developers to buy it.

Though some of these property editors are inconvenient or missing, it is important to note that the editors merely build the XAML code that defines the user interface. You can always edit the XAML manually to achieve effects that the Properties window does not support directly.

The following sections explain how to write XAML code and the Visual Basic code behind it.

Editing XAML

Figure 12-2 shows the IDE displaying a new WPF project. Most of the areas should look familiar from Windows Forms development. The Toolbox on the left contains tools that you can place on the window in the middle area. Solution Explorer on the right shows the files used by the application. The Properties window shows property values for the currently selected control in the middle. The selected object in Figure 12-2 is the main Window, so the top of the Properties window shows its type: System.Windows.Window.

The IDE looks almost the same for Windows Forms and WPF applications.

Figure 12.2. The IDE looks almost the same for Windows Forms and WPF applications.

One large difference between the IDE's appearance when building a WPF application versus a Windows Forms application is the central editor. In a Windows Forms application, you edit a form with the Windows Forms Designer. In a WPF application, you use the graphical XAML editor shown in Figure 12-2 to edit a Window object's XAML code. The upper half of this area shows a graphical editor where you can drag controls from the Toolbox much as you design a Windows Form. The lower part of the editor shows the resulting XAML code.

If you look closely at Figure 12-2, you can see the Window element that includes the rest of the file. When you first build an application, the Window object's element contains a single Grid control.

Usually, it is easiest to build WPF Window objects by using the graphical editor and the Toolbox. When you select a control in the graphical editor, you can view and modify many of its properties in the Properties window. Unfortunately, the Properties window does not give you access to all of the controls' features. Some properties are read-only in the Properties window or represent values that you cannot enter in the Properties window.

For example, a control's LayoutTransform property determines how the control is moved, scaled, rotated, and skewed before it is positioned and drawn. In the Properties window, this property appears as "Identity" if you have not defined a transformation, or the name of a class such as "System.Windows.Media.RotateTransform" if the transform rotates the control. Unfortunately, the Properties window does not provide any tools for setting the LayoutTransform property's value. If you want to set this property, you must type it into the XAML code by hand.

Figure 12-3 shows a Window containing a Button that is rotated 20 degrees. Notice that the LayoutTransform property in the Properties window displays the RotateTransform's class name System.Windows.Media.RotateTransform. The XAML code in the bottom left defines the control's LayoutTransform property.

XAML code can make a LayoutTransform but the Properties window cannot.

Figure 12.3. XAML code can make a LayoutTransform but the Properties window cannot.

Example program RotatedButton, which is available for download on the book's web site, uses the code shown in Figure 12-3 to display a rotated button.

Expression Blend provides additional tools for editing XAML. For example, it includes editors that let you define layout transformations interactively. Although the Visual Studio IDE doesn't provide similar tools, you can build these by hand with XAML code.

Similarly, the Properties window doesn't give you an easy way to set a non-container control's Content property to another control, but you can do this easily with XAML code. For example, to place a Grid inside a Button control, simply type the Grid control's definition between the Button control's start and end tags.

Example program GridButton uses the following XAML code to build a Button containing a Grid similar to the one shown in Figure 12-1:

<Window x:Class="Window1"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="XamlGridButton"
 Height="193" Width="219">
   <Grid>
     <Button Name="btnGrid" Height="100" Width="150">
       <Grid Height="90" Width="140">
         <Grid.RowDefinitions>
           <RowDefinition Height="33*" />
           <RowDefinition Height="33*" />
           <RowDefinition Height="33*" />
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
           <ColumnDefinition Width="33*" />
           <ColumnDefinition Width="33*" />
           <ColumnDefinition Width="33*" />
         </Grid.ColumnDefinitions>
         <Label Content="UL" Grid.Row="0" Grid.Column="0" />
         <Label Content="In The Middle" Grid.Row="1"
          Grid.Column="0" Grid.ColumnSpan="3"
          VerticalAlignment="Center" HorizontalAlignment="Center" />
         <Label Content="LR" Grid.Row="2" Grid.Column="2"
          VerticalAlignment="Bottom"
          HorizontalAlignment="Right" />
       </Grid>
     </Button>
   </Grid>
</Window>

                                                  
XAML code can make a LayoutTransform but the Properties window cannot.

The top-level Window element contains a Grid control that holds a single Button. The Button contains a second Grid control. Grid.Row and Grid.Column elements define the grid's row and column sizes.

The inner Grid contains three Label controls. The first displays the text UL, is aligned to the upper left (by default), and is contained in the grid's upper-left cell (row 0, column 0).

The second Label displays the text In the Middle, is aligned in the center, and is contained in grid's second row (row 1), first column (column 0). Its ColumnSpan property is 3, so it spans all three cells in the second row.

The final Label displays the text LR, is aligned to the lower right, and is in the grid's lower-right cell (row 2, column 2).

The graphical editor and the Properties window don't give you access to all of XAML's features, but they do let you build a basic user interface for WPF applications. Once you have defined the window's basic structure, you can use XAML to fine-tune the result (for example, by adding gradient backgrounds).

Editing Visual Basic Code

Each XAML file is associated with a Visual Basic code file. When you first create a WPF project, that file is opened by default. If you look closely at the central designer in Figure 12-3, you'll see that the XAML file Window1.xaml is open and visible in the designer. Another tab contains the corresponding Visual Basic file Window1.xaml.vb. Click that tab to view the Visual Basic source code.

The following text shows the Visual Basic source code initially created for a XAML file:

Class Window1

End Class

You can add event handlers to this file just as you can add event handlers to Windows Forms code. Use the left dropdown to select a control or Window1 Events. Then use the right drop-down list to select an event for that object.

You can also double-click a WPF control on the WPF Window Designer to create an event handler for that control's default event. This doesn't work with every control (such as Grid, Label, and StackPanel) but it works for those that are most likely to need event handlers (such as Button, CheckBox, ComboBox, RadioButton, and TextBox).

You can also add non-event handler subroutines and functions as you can in any other Visual Basic code file.

Inside the Visual Basic code file, you can get and set control properties and call control methods, just as you can in a Windows Forms project. The only differences are in the features the WPF controls provide. Those differences generally correspond to the XAML commands that define controls.

For example, the following Visual Basic code builds the same Button containing a Grid holding three Labels shown in Figure 12-1. The previous section, "Editing XAML," shows XAML code that builds this button.

Class Window1
    Private Sub Window1_Loaded() Handles Me.Loaded
        ' Make a grid.
        Dim grd As New Grid()
        grd.Width = btnGrid.Width - 10
        grd.Height = btnGrid.Height - 10

        ' Add rows and columns.
        AddRow(grd, New GridLength(33, GridUnitType.Star))
        AddRow(grd, New GridLength(33, GridUnitType.Star))
        AddRow(grd, New GridLength(33, GridUnitType.Star))
AddCol(grd, New GridLength(33, GridUnitType.Star))
        AddCol(grd, New GridLength(33, GridUnitType.Star))
        AddCol(grd, New GridLength(33, GridUnitType.Star))

        ' Put things inside the grid.
        Dim lbl1 As New Label()
        lbl1.Content = "UL"
        lbl1.HorizontalAlignment = Windows.HorizontalAlignment.Left
        lbl1.VerticalAlignment = Windows.VerticalAlignment.Top
        lbl1.SetValue(Grid.RowProperty, 0)
        lbl1.SetValue(Grid.ColumnProperty, 0)
        grd.Children.Add(lbl1)

        Dim lbl2 As New Label()
        lbl2.Content = "In the Middle"
        lbl2.HorizontalAlignment = Windows.HorizontalAlignment.Center
        lbl2.VerticalAlignment = Windows.VerticalAlignment.Center
        lbl2.SetValue(Grid.RowProperty, 1)
        lbl2.SetValue(Grid.ColumnProperty, 0)
        lbl2.SetValue(Grid.ColumnSpanProperty, 3)
        grd.Children.Add(lbl2)

        Dim lbl3 As New Label()
        lbl3.Content = "LR"
        lbl3.HorizontalAlignment = Windows.HorizontalAlignment.Right
        lbl3.VerticalAlignment = Windows.VerticalAlignment.Bottom
        lbl3.SetValue(Grid.RowProperty, 2)
        lbl3.SetValue(Grid.ColumnProperty, 2)
        grd.Children.Add(lbl3)

        ' Put the grid inside the button.
        btnGrid.Content = grd
    End Sub

     ' Add a row of the indicated height to the grid.
    Private Sub AddRow(ByVal my_grid As System.Windows.Controls.Grid, _
     ByVal height As GridLength)
        Dim row_def As New RowDefinition()
        row_def.Height = height
        my_grid.RowDefinitions.Add(row_def)
    End Sub

    ' Add a column of the indicated width to the grid.
    Private Sub AddCol(ByVal my_grid As System.Windows.Controls.Grid, _
     ByVal width As GridLength)
       Dim col_def As New ColumnDefinition()
       col_def.Width = width

       my_grid.ColumnDefinitions.Add(col_def)
   End Sub
Private Sub btnGrid_Click() Handles btnGrid.Click
      MessageBox.Show("Clicked!", "Clicked", _
          MessageBoxButton.OK, _
          MessageBoxImage.Information)
   End Sub
End Class

                                                  
Editing Visual Basic Code

The main Window class's Loaded event handler fires when the form is loaded. The code starts by creating a Grid control and setting its width and height.

Next, the code calls subroutines AddRow and AddCol to make three rows and columns. These routines make building rows and columns easier, and are described shortly.

The code then creates three Label controls and sets their properties. Some properties, such as HorizontalAlignment and Content, are fairly straightforward. Other properties, such as Grid.RowProperty, Grid.ColumnProperty, and Grid.ColumnSpan, are a little trickier. Those properties only make sense when the Label controls are contained in a Grid, so they are not really properties of the Label controls. Instead they are properties added by the Grid control's SetValue method, much as an ExtenderProvider adds properties to a control. If you place a Button inside a StackPanel, the Properties window doesn't show these properties.

After it initializes each Label, the code uses the Grid control's Children.Add method to put the Label inside the Grid.

After it finishes creating all of the controls, the code sets the Button control's Content property to the new grid.

Subroutine AddRow creates a new RowDefinition object to represent a Grid's row. It sets the object's Height and adds the object to the Grid control's RowDefinitions collection. Subroutine AddCol uses similar methods to make a new Grid column.

The last piece of code in this example is a Click event handler for the btnGrid button. When you click the button, this code displays a message box.

Anything you can do declaratively with XAML you can also do procedurally with Visual Basic. The following section, "XAML Features," describes some of the things that you can do with XAML and shows examples. The section "Procedural WPF" later in this chapter explains how you can implement some of the same features with Visual Basic code instead of XAML.

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.

Note

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 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/ms752059.aspx and the Windows Presentation Foundation development page at msdn2.microsoft.com/ms754130.aspx. My book WPF Programmer's Reference (Wrox, Stephens, 2010, www.amazon.com/exec/obidos/ASIN/0470477229/vbhelper) also provides lots of examples of useful techniques. If you discover other sources of good examples, email me at and I'll post them on the book's web site.

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:Class="Window1"
 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="Red">

   </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:Class="Window1"
 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>

                                                  
Objects

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.

Note

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 first column is 50 pixels wide and the other two columns each take up 50 percent of the remaining width.

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

When you use a * in measurements, the control divides its height or width proportionally among items that contain a *. For example, if a grid has two rows with height 50*, they each get half of the control's height. If the two rows had heights 10* and 20*, the first would be half as tall as the second.

If the control also contains items without a *, their space is taken out first. For example, suppose a grid defines rows with heights 10, 20*, and 30*. Then the first row has height 10, the second row gets 20/50 of the remaining height, and the third row gets the rest.

Note

Most of the examples in this chapter use values that are at least close to percentages because they're easier to understand.

An object element's body 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 Height="33*" />
    <RowDefinition Height="33*" />
    <RowDefinition Height="33*" />
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
    <ColumnDefinition Width="33*" />
    <ColumnDefinition Width="33*" />
    <ColumnDefinition Width="33*" />
   </Grid.ColumnDefinitions>
   <Button Grid.Row="0" Grid.Column="0" Margin="5">0,0</Button>
   <Button Grid.Row="0" Grid.Column="1" Margin="5">0,1</Button>
   <Button Grid.Row="0" Grid.Column="2" Margin="5">0,2</Button>
   <Button Grid.Row="1" Grid.Column="0" Margin="5">1,0</Button>
   <Button Grid.Row="1" Grid.Column="1" Margin="5">1,1</Button>
   <Button Grid.Row="1" Grid.Column="2" Margin="5">1,2</Button>
   <Button Grid.Row="2" Grid.Column="0" Margin="5">2,0</Button>
   <Button Grid.Row="2" Grid.Column="1" Margin="5">2,1</Button>
   <Button Grid.Row="2" Grid.Column="2" Margin="5">2,2</Button>
</Grid>

                                                  
Objects

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 but not quite what you want. For example, if you size and position a control by using click and drag, the editor may set its Margin property to 10,10,11,9 when you really want 10,10,10,10 (or just 10).

It can also sometimes be hard to place controls exactly where you want them. You can fix some of these values in the Properties window, but sometimes it's just easier to edit the XAML code directly.

Resources

Example program Calculator, which is available for download on the book's web site, is shown in Figure 12-4. 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 drawn with RadialGradientBrush objects. The CE, C, and = buttons have blue backgrounds, and the operator buttons have green backgrounds.

This program uses resources to simplify maintenance.

Figure 12.4. 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 used by the brushes (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 objects. In this example, you can define resources to represent button backgrounds, and then use those resources to set each button's Background property. If you later need to change the background, you only need to update the resources.

The following code shows how the calculator application shown in Figure 12-4 creates a LinearGradientBrush resource called brResult, which the program uses to draw the result text box at the top. Ellipses show where code has been omitted to make it easier to read.

<Window x:Class="Window1"
 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>

                                                  
This program uses resources to simplify maintenance.

The Window element contains a Window.Resources tag that contains the resource definitions. The LinearGradientBrush element defines the brush. One of this element's more important attributes is x:Key, 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>

                                                  
This program uses resources to simplify maintenance.

Later if you decide to change the background color for the result label, you only need to change the definition of the brResult resource. This example only uses that resource for one label so you don't save a huge amount of work by defining a resource. The program's buttons, however, reuse the same resources many times. Instead of reusing the background resources directly, however, the buttons use styles as described in the next section.

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>

                                                  
Styles

The following code defines a style named styClear for the calculator's C, CE, and = buttons:

<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>

                                                  
Styles

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 (set to the brush resource brClear) 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 to increase the margin above these buttons.

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

Styles let the program keep all of the common properties for a set of controls in a single location. Now if you decided to change the color of the C, CE, and = buttons, you would only need to change the definition of the brClear brush. If you wanted to change the brushes' margins, you would only need to change the styClear style.

As the previous code shows, styles also keep 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 down, 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>

                                                  
Templates

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 also 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.

Example program ButtonTemplate uses the following code to create 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>

                                                  
Templates

The Window contains a Grid that holds six buttons. The first four buttons do not explicitly set their Style, so they use the previously defined octagonal 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. That style also positions the button's text in the upper center. When you hover the mouse over these buttons, 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!"

Example program ButtonTemplate demonstrates this code. Download the example to see how the triggers work.

Figure 12-5 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.

Templates let you change the appearance and behavior of objects such as buttons.

Figure 12.5. Templates let you change the appearance and behavior of objects such as buttons.

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>

                                                  
Transformations

XAML also provides TranslateTransform and ScaleTransform elements that let you translate and scale an object. Example program RotatedButton, which is available for download on the book's web site and shown in Figure 12-6, uses transformations to draw several buttons that have been rotated and scaled vertically and horizontally.

Buttons can be rotated and scaled vertically and horizontally by using RotateTransform and ScaleTransform.

Figure 12.6. 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 and a Storyboard object to control it. A Storyboard contains information about the state the animation should have at various times during the animation.

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
         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>

                                                  
Animations

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 a new kind of event developed for WPF. Routed events travel up and down through a WPF application's hierarchy of controls so interested controls can catch and process the events. For simple purposes, however, a routed event behaves much like a Windows Forms event does and 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 tasks that the trigger should perform when it runs. In this example, the trigger performs the BeginStoryboard action. Inside the BeginStoryboard element is a Storyboard element that represents the things that the storyboard should do.

The Storyboard element's 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 a value of 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 like. 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>

                                                  
STORYBOARD START

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>
  <!-- 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>

                                                  
STORYBOARD START

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: Line, Ellipse, Rectangle, Polygon, Polyline, and Path.

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 draws a line from (10, 10) to (90, 90) and another from (90, 10) to (10, 90):

<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" />

                                                  
Line

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

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

Ellipse

The Ellipse object draws an ellipse. The following code draws an ellipse filled with a LinearGradientBrush:

<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>

                                                  
Ellipse

Rectangle

The Rectangle object draws a rectangle. The syntax is similar to that used to draw ellipses. The following code draws a rectangle filled with a LinearGradientBrush:

<Rectangle Margin="2,20,2,20"
  Grid.Column="2" Grid.Row="0"
  Stroke="Orange" StrokeThickness="5">
  <Rectangle.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>
  </Rectangle.Fill>
</Rectangle>

This code is exactly the same as the ellipse code except it uses the keyword Rectangle instead of Ellipse.

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:

<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" />

                                                  
Polygon

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 a series of four dashed lines:

<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>

                                                  
Polyline

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 (Line, Ellipse, and so forth) 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 a point's 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.

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 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:

A 50,20 0 1 0 60,80

 

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

 

Z or z

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

Z

Example program Shapes, which is available for download on the book's web site, demonstrates several different Path objects.

Example program BezierCurves, shown in Figure 12-7, shows examples of the four different kinds of Bezier curves. This program also draws a gray polyline to show the curves' parameters.

The Path object can draw Bezier curves.

Figure 12.7. 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 12-7 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. 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.

PROCEDURAL WPF

The previous sections explain how to use XAML to build WPF windows. By using XAML, you can define controls, resources, styles, templates, transformations, and even animations.

Behind the scenes, an application reads the XAML code, and then builds corresponding controls and other objects to make the user interface. Often, it's easiest to build forms by using the XAML editor, but if necessary, your Visual Basic code can build exactly the same objects.

For example, the following Visual Basic code adds a button to a WPF form:

' The button we will build.
Private WithEvents btnClickMe As Button

' Build the user interface.
Private Sub Window1_Loaded() Handles Me.Loaded
 ' Get the window's default Grid.
    Dim grd As Grid = DirectCast(Me.Content, Grid)

    ' Add a Button.
    btnClickMe = New Button()
    btnClickMe.Content = "Click Me"
    btnClickMe.Margin = New Thickness(5)
    grd.Children.Add(btnClickMe)
End Sub

' The user clicked the button.
Private Sub btnClickMe_Click() Handles btnClickMe.Click
    MessageBox.Show("Clicked!")
End Sub

The code starts by converting the window's Content property into a Grid object. It then creates a Button, sets a couple of properties for it, and adds it to the Grid control's Children collection.

The Button control's variable is declared at the module level and includes the WithEvents keyword so it is easy to catch the button's Click event.

Example program ProceduralAnimatedButton uses Visual Basic code to implement several of the techniques described earlier using XAML code. It creates a brush object and uses it to define a Style for buttons. It then creates three Buttons using that Style.

When the mouse moves over a button, the program's code builds and plays an animation to enlarge the button. When the mouse moves off of the button, the code restores the button to its original size.

The following code builds the user interface objects when the program's window loads:

Private WithEvents btnCenter As Button
Private Const BIG_SCALE As Double = 1.5

Private Sub Window1_Loaded() Handles Me.Loaded
    ' Make a style for the buttons.
    Dim br_button As New RadialGradientBrush( _
        Colors.HotPink, Colors.Red)
    br_button.Center = New Point(0.5, 0.5)
    br_button.RadiusX = 1
    br_button.RadiusY = 1

    Dim style_button As New Style(GetType(Button))
    style_button.Setters.Add(New Setter(Control.BackgroundProperty, _
        br_button))
    style_button.Setters.Add(New Setter(Control.WidthProperty, CDbl(70)))
    style_button.Setters.Add(New Setter(Control.HeightProperty, CDbl(40)))
    style_button.Setters.Add(New Setter(Control.MarginProperty, _
        New Thickness(5)))

    ' Set the transform origin to (0.5, 0.5).
    style_button.Setters.Add(New Setter( _
        Control.RenderTransformOriginProperty, New Point(0.5, 0.5)))

    ' Make a StackPanel to hold the buttons.
    Dim stack_panel As New StackPanel()
    stack_panel.Margin = New Thickness(20)

    ' Add the Left button.
    Dim btn_left As Button
    btn_left = New Button()
    btn_left.Style = style_button
    btn_left.Content = "Left"
    btn_left.RenderTransform = New ScaleTransform(1, 1)
    btn_left.SetValue( _
        StackPanel.HorizontalAlignmentProperty, _
        Windows.HorizontalAlignment.Left)
    AddHandler btn_left.MouseEnter, AddressOf btn_MouseEnter
    AddHandler btn_left.MouseLeave, AddressOf btn_MouseLeave
    stack_panel.Children.Add(btn_left)

    ' Make the Center button.
    btnCenter = New Button()
    btnCenter.Style = style_button
    btnCenter.Content = "Center"
    btnCenter.RenderTransform = New ScaleTransform(1, 1)
    btnCenter.SetValue( _
        StackPanel.HorizontalAlignmentProperty, _
        Windows.HorizontalAlignment.Center)
    AddHandler btnCenter.MouseEnter, AddressOf btn_MouseEnter
    AddHandler btnCenter.MouseLeave, AddressOf btn_MouseLeave
    stack_panel.Children.Add(btnCenter)
' Make the Right button.
    Dim btn_right As New Button
    btn_right.Style = style_button
    btn_right.Content = "Right"
    btn_right.RenderTransform = New ScaleTransform(1, 1)
    btn_right.SetValue( _
        StackPanel.HorizontalAlignmentProperty, _
        Windows.HorizontalAlignment.Right)
    AddHandler btn_right.MouseEnter, AddressOf btn_MouseEnter
    AddHandler btn_right.MouseLeave, AddressOf btn_MouseLeave
    Stack_panel.Children.Add(btn_right)

    Me.Content = stack_panel
End Sub

                                                  
KEEP YOUR DISTANCE

This code starts by declaring a Button control using the WithEvents keyword. The program makes three buttons, but only catches the Click event for this one. The code also defines a constant that determines how large the button will grow when it enlarges.

When the window loads, the code creates a RadialGradientBrush and defines its properties. It then creates a Style object that can apply to Button objects. It adds several Setter objects to the Style to set a Button control's Background, Width, Height, Margin, and RenderTransformOrigin properties.

Next, the code creates a StackPanel object. This will be the window's main control and will replace the Grid control that Visual Studio creates by default.

The program then makes three Button objects. It sets various Button properties, including setting the Style property to the Style object created earlier. It also sets each Button control's RenderTransform property to a ScaleTransform object that initially scales the Button by a factor of 1 vertically and horizontally. It will later use this transformation to make the Button grow and shrink.

The code uses each Button control's SetValue method to set its HorizontalAlignment property for the StackPanel. The code uses AddHandler to give each Button an event handler for its MouseEnter and MouseLeave events. Finally, the code adds the Button controls to the StackPanel's Children collection.

The window's Loaded event handler finishes by setting the window's Content property to the new StackPanel containing the Button controls.

The following code shows how the program responds when the mouse moves over a Button:

' The mouse moved over the button.
' Make it larger.
Private Sub btn_MouseEnter(ByVal btn As Button, _
 ByVal e As System.Windows.Input.MouseEventArgs)
    ' Get the button's transformation.
    Dim scale_transform As ScaleTransform = _
        DirectCast(btn.RenderTransform, ScaleTransform)

    ' Create a DoubleAnimation.
Dim ani As New DoubleAnimation(1, BIG_SCALE, _
        New Duration(TimeSpan.FromSeconds(0.15)))

    ' Create a clock for the animation.
    Dim ani_clock As AnimationClock = ani.CreateClock()

    ' Associate the clock with the transform's
    ' ScaleX and ScaleY properties.
    scale_transform.ApplyAnimationClock( _
        ScaleTransform.ScaleXProperty, ani_clock)
    scale_transform.ApplyAnimationClock( _
        ScaleTransform.ScaleYProperty, ani_clock)
End Sub

                                                  
KEEP YOUR DISTANCE

This code first gets the button's ScaleTransform object. It then creates a DoubleAnimation object to change a value from 1 to the BIG_SCALE value (defined as 1.5 in the earlier Const statement) over a period of 0.15 seconds. It uses the object's CreateClock statement to make an AnimationClock to control the animation. Finally, the code calls the ScaleTransformation object's ApplyAnimationClock method twice, once for its horizontal and vertical scales. The result is that the Button control's ScaleTransform object increases the Button control's scale vertically and horizontally.

The btn_MouseLeave event handler is very similar, except that it animates the Button controls' scale values shrinking from BIG_SCALE to 1.

Example program GrowingButtons uses a similar technique to enlarge and shrink Button controls. Instead of using a simple DoubleAnimation to enlarge the Button controls, however, it uses DoubleAnimationUsingKeyFrames. This object lets you define a series of values that the animation should visit.

The following code shows how this program's MouseEnter event handler works:

Private Const BIG_SCALE As Double = 1.75
Private Const END_SCALE As Double = 1.5

Private Sub btn_MouseEnter(ByVal btn As Button, _
 ByVal e As System.Windows.Input.MouseEventArgs)
    ' Get the button's transformation.
    Dim scale_transform As ScaleTransform = _
        DirectCast(btn.RenderTransform, ScaleTransform)

    ' Create a DoubleAnimation that first
    ' makes the button extra big and then
    ' shrinks it to the "normal" big size.
    Dim ani As New DoubleAnimationUsingKeyFrames()
    Dim fr1 As New SplineDoubleKeyFrame(1.0, KeyTime.FromPercent(0.0))
    Dim fr2 As New SplineDoubleKeyFrame(BIG_SCALE, KeyTime.FromPercent(0.5))
    Dim fr3 As New SplineDoubleKeyFrame(END_SCALE, KeyTime.FromPercent(1.0))
    ani.KeyFrames.Add(fr1)
    ani.KeyFrames.Add(fr2)
ani.KeyFrames.Add(fr3)
    ani.Duration = New Duration(TimeSpan.FromSeconds(0.33))

    ' Create a clock for the animation.
    Dim ani_clock As AnimationClock = ani.CreateClock()
    'Dim ani_clock As AnimationClock = ani.CreateClock()

    ' Associate the clock with the transform's
    ' ScaleX and ScaleY properties.
    scale_transform.ApplyAnimationClock( _
       ScaleTransform.ScaleXProperty, ani_clock)
    scale_transform.ApplyAnimationClock( _
       ScaleTransform.ScaleYProperty, ani_clock)

    ' Pop the button to the top of the stacking order.
    grdMain.Children.Remove(btn)
    grdMain.Children.Add(btn)
End Sub

                                                  
KEEP YOUR DISTANCE

Instead of simply growing the Button's scale factors from 1 to 1.5, the animation first makes the Button grow by a factor of 1.75, and then shrink to a growth factor of 1.5. This overshoot gives the Button a cartoon-like style that is popular in some user interfaces.

After it finishes animating the Button, the code removes the Button from the main Grid control's Children collection, and then re-adds it to the collection to make the Button appear above the other Buttons so it covers parts of its neighbors.

Figure 12-8 shows example program GrowingButtons in action with the mouse resting over the Tuesday button.

Program GrowingButtons uses Visual Basic code to animate buttons.

Figure 12.8. Program GrowingButtons uses Visual Basic code to animate buttons.

Other examples available for download on the book's web site demonstrate other procedural WPF techniques. For example, program ProceduralCalculator builds a calculator similar to the one shown in Figure 12-4, but it builds its user interface in Visual Basic code. Example program GridButtonCode uses Visual Basic code to build a button that holds a grid similar to the one shown in Figure 12-1.

DOCUMENTS

WPF includes three different kinds of documents: flow documents, fixed documents, and XPS (XML Paper Specification) documents. These different kinds of documents provide support for high-end text and printing capabilities.

For example, fixed documents allow you to generate a document that keeps the same layout whether it is viewed on a monitor, printed at low-resolution, or printed at a very high-resolution. On each device, the document uses the features available on that device to give the best result possible.

Each of these three kinds of documents is quite complex so there isn't room to do them justice here. However, the following three sections provide an overview and give brief examples.

Flow Documents

Flow documents are designed to display as much data as possible in the best way possible, depending on runtime constraints such as the size of the control displaying the document. If the control grows, the document rearranges its contents to take advantage of the new available space. If the control shrinks, the document again rearranges its contents to fit the available space. The effect sort of mimics the way a web browser behaves, rearranging the objects it displays as it is resized.

The WPF FlowDocument control represents a flow document. The FlowDocument can contain four basic content elements: List, Section, Paragraph, and Table. These have rather obvious purposes: to display data in a list, group data in a section, group data in a paragraph, or display data in a table.

Although the main emphasis of these elements is on text, they can contain other objects. For example, a Paragraph can contain controls such as Button, Label, TextBox, and Grid controls. It can also contain shapes such as Polygon, Ellipse, and Path.

A fifth content element, BlockUIElement, can hold user interface controls such a Button, Label, and TextBox. A BlockUIElement can hold only one child, but if that child is a container such as a Grid or StackPanel it may contain other controls.

WMF provides three types of objects for displaying FlowDocuments: FlowDocumentReader, FlowDocumentPageViewer, and FlowDocumentScrollViewer.

The FlowDocumentReader lets the user pick from three different viewing modes: single page, book reading, and scrolling. In single page mode, the reader displays the document one page at a time. The object determines how big to make a page based on its size. If the reader is wide enough, it will display the FlowDocument in two or more columns, although it still considers its surface to hold a single page at a time, even if that page uses several columns.

In book reading mode, the reader displays two pages at a time. The object divides its surface into left and right halves, and fills each with a "page" of data. The reader always displays two pages, no matter how big or small it is.

In scrolling mode, the reader displays all of the document's contents in a single long page, and it provides a scroll bar to allow the user to scroll down through the document. This is similar to the way web browsers handle a very tall web page.

Example program UseFlowDocumentReader, shown in Figure 12-9 and available for download on the book's web site, shows a FlowDocumentReader object displaying a document in book reading mode. The program's View menu lets you change the viewing mode.

This FlowDocumentReader is using book reading mode.

Figure 12.9. This FlowDocumentReader is using book reading mode.

This program demonstrates several useful features of FlowDocument objects. The section headers are contained in Paragraph objects that use a Style that defines their font. If you wanted to change the appearance of all of the headers, you would only need to change the Style.

The FlowDocument uses a LinearGradientBrush that shades from black to gray as the text moves left to right. (The effect is more striking on a monitor if you use a colored gradient.)

The document contains a table in its first section, Button and TextBox controls, an Ellipse, and a Grid that holds a Polygon. It uses the Floater element to allow another Grid containing a Polygon and a text caption to float to a position where it will fit nicely in the display. The document also holds a list, one item of which contains a Polygon drawing a triangle.

The bottom of the FlowDocumentReader displays a toolbar. If you click the magnifying glass button on the left, a search text box appears next to it. You can enter text to search for, and the reader will let you scroll back and forth through any matches.

In the middle of the toolbar, the reader displays the current page number and the total number of pages. The three buttons to the right let the user select the single page, book reading, and scrolling views. Finally, the slider on the lower right lets the user adjust the document's scale to zoom in or out.

The FlowDocumentPageViewer and FlowDocumentScrollViewer objects behave as the FlowDocumentReader does in its single page and scrolling modes, respectively. (The big difference is that FlowDocumentReader can display documents in several modes while the others use only one. If you want to allow the reader more options, use FlowDocumentReader. If you want to restrict the view available, use one of the other kinds of viewers.)

Example programs UseFlow DocumentPageViewer and UseFlowDocumentScrollViewer, which are available for download on the book's web site, demonstrate these controls.

Note

If you display a FlowDocument element itself, it acts as a FlowDocumentReader. See example program UseFlowDocument, which is available for download on the book's web site.

Fixed Documents

A FixedDocument represents a document that should always be displayed exactly as it was originally composed. Whereas a FlowDocument rearranges its content to take advantage of its current size, all of the content in a FixedDocument remains where it was originally placed. If a FlowDocument is similar to a web browser, then a FixedDocument is similar to an Adobe Acrobat PDF document.

The FixedDocument object contains one or more PageContent objects, each containing a FixedPage object. It is in the FixedPage object that you place your content. You can use the usual assortment of containers to arrange controls and other objects inside the FixedPage object.

A program can use a DocumentViewer to display a FixedDocument. The DocumentViewer provides tools to let the user print, zoom in and out, size the document to fit the viewer, display the document in one- or two-page modes, and search for text within the document.

Example program UseFixedDocument, which is available for download on the book's web site, displays a FixedDocument inside a DocumentViewer.

XPS Documents

In addition to flow documents and fixed documents, WPF also defines a third kind of document called XML Paper Specification (XPS) documents. XPS is an XML-based open standard used to represent fixed documents.

An XPS document is stored in a file called a package. The package is made up of pieces called parts. Physically, the parts are arranged as files and folders. When you save the document to disk, it is stored as a ZIP-compressed collection of these physical files and folders. If you change the file's extension from .xps to .zip, you can read the files using any ZIP-enabled viewer. For example, Windows Explorer will let you browse through the ZIP file.

Logically, the document's parts form a hierarchical representation of the document. (Remember that the document uses an XML format, and XML is hierarchical, so the document is also hierarchical.) The document itself may contain a FixedDocumentSequence object that contains one or more FixedDocument objects. The FixedDocument objects are similar to the ones described in the previous section, so they can hold container controls that contain any number of objects arranged in a hierarchical way.

In addition to the features provided by FixedDocuments, XPS documents also allow you to digitally sign the package. That tells others that you signed it, gives them the time and date that you signed it, and ensures that the document has not been modified since then. A document can contain more than one signature and you can provide different levels of security on different parts of the document. For example, you could prevent others from changing the document's body, but allow them to add annotations.

Like the other new WPF document objects, XPS documents are quite complex, and there isn't room to do them justice here. See Microsoft's online help (msdn2.microsoft.com/system.windows.xps and www.microsoft.com/whdc/xps/xpsspec.mspx are good places to start) and search the Web for more detailed information and examples.

Note

Note that many of the WPF examples scattered around the Web were written in early betas and no longer work exactly as they were originally posted. You may need to perform some conversions to make them work properly.

SUMMARY

One of the main goals of WPF is to separate the user interface more completely from the code behind it. XAML lets you declaratively build a user interface, and then later add code to handle the events that any Windows application needs to perform. Because the user interface is separate from the code, you can assign different developers to work on each of them. You can have a graphics designer use a graphical XAML editor to build the user interface, and have a Visual Basic developer write the underlying code. Later, the graphical designer can modify the user interface without forcing you to rewrite the code.

WPF also includes hundreds of new objects for defining user interfaces. These objects let you build windows that take advantage of modern computer graphics hardware, and can provide advanced features such as translucency and rotated controls. New drawing objects produce complex graphics in two and three dimensions.

Resources and styles let you customize objects so that they are easy to change in a central location. Triggers, animations, and storyboards let the interface interact with the user at a very high level, so the bulk of your code doesn't need to handle these more cosmetic chores.

New document objects let you display information that can flow to take best advantage of the available space, or that remain in fixed positions on any display device. Powerful document viewers let users scroll through documents, zoom in and out, print, and copy data to the clipboard.

WPF provides a huge number of powerful new features, and this chapter barely scratches the surface.

In Windows Forms applications, Form objects play a special role. They represent the top-level user interface components in which all other controls reside.

In a WPF application, the situation is a little less obvious. A top-level object in a WPF application can be a Window, which roughly corresponds to a Form, but it can also be a Page, PageFunction, or FlowDocument. Chapter 13, "WPF Windows," describes the Windows class and these other top-level classes, and explains their special roles in WPF applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.234.118