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
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
Content property to the text that you want to display.
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: 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.
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
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: 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
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: 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.
Window element contains the single
Grid element. That element contains a
Grid.Background element that in turn contains a
LinearGradientBrush element. The
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.
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
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>
Window element contains a
Grid control that holds a single
Button contains a second
Grid.Column elements define the grid's row and column sizes.
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).
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.
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
StackPanel) but it works for those that are most likely to need event handlers (such as
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
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
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
Content, are fairly straightforward. Other properties, such as
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
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
Children.Add method to put the
Label inside the
After it finishes creating all of the controls, the code sets the
Content property to the new grid.
AddRow creates a new
RowDefinition object to represent a
Grid's row. It sets the object's
Height and adds the object to the
RowDefinitions collection. Subroutine
AddCol uses similar methods to make a new
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.