Wrox Home  
Search
Visual Basic 2008 Programmer's Reference
by Rod Stephens
February 2008, Paperback


Excerpt from Visual Basic 2008 Programmer's Reference

Using WPF Controls in Visual Basic 2008

by Rod Stephens

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. First, the new controls take better advantage of the graphics capabilities of modern computer hardware and software. They can more easily provide graphical effects such as transparent or translucent backgrounds, gradient shading, rotation, two- and three-dimensional appearance, multimedia, and other effects.

The second main goal of WPF is 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 Visual Studio 2008 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 text file that uses a language called XAML (pronounced zammel). XAML is a special form of Extensible Markup Language (XML) that defines user interface elements such as buttons, labels, container controls, backgrounds, colors, fonts, styles, and other control attributes.

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.

By keeping the user interface and the code as separate as possible, WPF allows more than one developer to work on the same form at the same time. For example, a graphics designer can use a tool called Expression Design 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.

You can learn more about Expression Design and download a trial version at http://www.microsoft.com/expression/products/overview.aspx.

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.

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. While 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, 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 1 shows a Button containing a Grid control that holds three labels.

Figure 1
Figure 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 Form Designer and the WPF Window Designer differ in detail. While 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 some Boolean properties with check boxes. It represents other properties that take enumerated values by text boxes where you can type values (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 Form designer does.

Some of these features may be addressed in later versions of Visual Studio. Others may be places where Microsoft has reserved more advanced features for Expression Design users, and still others may simply represent a change in Visual Studio's design philosophy.

While 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 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 2 is the main Window, so the top of the Properties window shows its type: System.Windows.Window.

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 Form Designer. In a WPF application, you use the graphical XAML editor shown in Figure 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 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. Others are editable, but do not provide custom editors.

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

For example, a control's Background property determines how the control draws its background. In the Properties window, you can set this to a single color's name, such as Red or a numeric color value such as #FF0000FF (blue). However, this property can also be set to a brush that the control should use to draw its background. For example, the brush can be a LinearGradientBrush, RadialGradientBrush, or PathGradientBrush object. You can set these kinds of values in the XAML code, or by using Visual Basic code, but the Properties window does not provide an editor that can do this, at least in the current release.

Figure 3 shows a Window containing a Grid that has a linear gradient background. Notice that the Background property in the Properties window displays the brush's class name System.Windows.Media.LinearGradientBrush. The XAML code in the bottom left defines the Grid control's background.

Figure 3
Figure 3: XAML code can make a gradient background while the Properties window cannot.

The following code shows the XAML that gives the grid its background:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="GradientBackground" Height="150" Width="200"
    >
    <Grid >
      <Grid.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
          <GradientStop Color="Red" Offset="0" />
          <GradientStop Color="White" Offset="0.5" />
          <GradientStop Color="Blue" Offset="1" />
        </LinearGradientBrush>
      </Grid.Background>
    </Grid>
</Window>

Example program GradientBackground, which is available as part of the Chapter 12 code download for the book Visual Basic 2008 Programmer's Reference (Wrox, 2008, ISBN: 978-0-470-18262-8), uses this code to fill a Grid with a gradient background.

The Window element contains the single Grid element. That element contains a Grid.Background element that in turn contains a LinearGradientBrush element. The StartPoint and EndPoint attributes give the points across which the gradient should vary its colors. The scale for these points ranges from (0, 0) in the control's upper-left corner to (1, 1) in its lower-right corner.

The GradientStop elements inside the LinearGradientBrush element indicate desired colors at points between the starting point and the ending point. The values shown here indicate that the background should start red at the StartPoint, shade to white halfway to the EndPoint, and shade from white to blue at the EndPoint.

Changes to the XAML code are not always immediately reflected in the graphical designer. Click the Design tab to make the designer reload the XAML code.

Expression Design provides additional tools for editing XAML. For example, it includes editors that let you define gradient backgrounds and Bezier curves interactively. Although the Visual Studio IDE doesn't provide similar tools, you can build these non-interactively 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, which is available for download on the book's Web site, uses the following XAML code to build a Button containing a Grid similar to the one shown in Figure 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>

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 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 on 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 drop-down 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 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

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

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.

This article is excerpted from Chapter 12, "Using WPF Controls," of Wrox's Visual Basic 2008 Programmer's Reference (Wrox, 2008, ISBN: 978-0-470-18262-8), by Rod Stephens. Rod is a Microsoft Visual Basic Most Valuable Professional (MVP) and ITT adjunct instructor. He has written 16 books that have been translated into half a dozen different languages, and more than 200 magazine articles covering Visual Basic, Visual Basic for Applications, Delphi, and Java. He is currently a regular contributor to DevX (www.DevX.com). Rod's popular VB Helper Web site http://www.vb-helper.com receives several million hits per month and contains thousands of pages of tips, tricks, and example code for Visual Basic programmers. Rod's other recent articles on wrox.com are LINQ Extension Methods with Visual Basic 2008 also fro the Visual Basic 2008 Programmer's Reference and Executing SQL Statements at Runtime in VB 2005 from his book Expert One-on-One Visual Basic 2005 Design and Development.