
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1082 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
}
// Show suggestions.
MessageBox.Show(spellingHints, "Try these instead");
}
}
The code is quite simple. We simply figure the current location of the caret in the text box using the CaretIndex property in order to extract a SpellingError object. If there is an error at said location (meaning the value is not null), we loop over the list of suggestions via the aptly named Suggestions property. Finally, we display the possibilities using a simple MessageBox.Show() request. Figure 29-15 shows a possible test run when the caret is within the misspelled word “auromatically.”
Figure 29-15. A custom spell checker!
Working with the PasswordBox Type
The PasswordBox type, not surprisingly, allows you to define a safe place to enter sensitive text data. By default, the password character is a circle type; however, this can be changed using the PasswordChar property. To obtain the value entered by the end user, simply check the Password property. Let’s update our current spell-checking application by requiring the correct password to see the list of spelling suggestions. First, update your existing <StackPanel> with a nested <StackPanel> that places the PasswordBox horizontally alongside the existing <Button>:
<StackPanel>
<Label FontSize ="15">Is this word spelled correctly?</Label> <TextBox SpellCheck.IsEnabled ="True" AcceptsReturn ="True"
Name ="txtFavoriteColor" FontSize ="14" BorderBrush ="Blue" Height ="100">
</TextBox>
<StackPanel Orientation ="Horizontal">
<PasswordBox Name ="pwdText" BorderBrush ="Black" Width ="100"></PasswordBox> <Button Name ="btnOK" Content ="Get Selections"
Width = "100" Click ="btnOK_Click"/> </StackPanel>
</StackPanel>

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1083 |
Now update your current Button Click event handler to make a call to a helper function named CheckPassword(), which tests against a hard-coded string. Be sure to only allow the suggestions to be presented if the check is successful. Here are the relevant updates:
public partial class MainWindow : System.Windows.Window
{
...
protected void btnOK_Click(object sender, RoutedEventArgs args)
{
if (CheckPassword())
{
// Same spell-checking logic as before...
}
else
MessageBox.Show("Security error!!");
}
private bool CheckPassword()
{
if (pwdText.Password == "Chucky") return true;
else
return false;
}
}
Beyond TextBox and PasswordBox, do be aware that if you are building an application that has a text area that can contain any type of content (graphical renderings, text, etc.), WPF also provides the RichTextBox. Furthermore, if you require the horsepower to build an extremely text-intensive application, WPF provides an entire document presentation API represented primarily within the
System.Windows.Documents namespace.
Here you will find types that allow you to build flow documents, which allow you to programmatically represent (in XAML or C# code) paragraphs, sections of related text blocks, sticky notes, annotations, tables, and other rich document-centric types. This edition of the text does not cover the RichTextBox or the flow document API, however; be sure to consult the .NET Framework 3.5 SDK documentation for further details if you are so inclined.
■Source Code The TextControls project is included under the Chapter 29 subdirectory.
That wraps up our initial look at the WPF control set. You’ll see how to build menu systems, status bars, and toolbars later in this chapter. The next task, however, is to learn how to arrange UI elements within a Window type using any number of panel types.
Controlling Content Layout Using Panels
A real-world WPF application invariability contains a good number of UI elements (user input controls, graphical content, menu systems, status bars, etc.) that need to be well organized within the containing window. As well, once the UI widgets have been placed in their new home, you will want to make sure they behave as intended when the end user resizes the window or possibly a portion of the window (as in the case of a splitter window). To ensure your WPF controls retain their position within the hosting window, we are provided with a good number of panel types.

1084 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
As you may recall from the previous chapter, when you place content within a window that does not make use of panels, it is positioned dead center within the container. Consider the following simple window declaration containing a single Button type. Regardless of how you resize the window, the UI widget is always equidistant on all four sizes of the client area.
<!-- This button is in the center of the window at all times-->
<Window x:Class="MyWPFApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Fun with Panels!" Height="285" Width="325">
<Button Name="btnOK" Height = "100" Width="80">OK</Button> </Window>
Also recall that if you attempt to place multiple elements directly within the scope of a <Window>, you will receive markup and/or compile-time errors. The reason for these errors is that a window (or any descendant of ContentControl for that matter) can assign only a single object to its Content property:
<!-- Error! Content property is implicitly set more than once!-->
<Window x:Class="MyWPFApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Fun with Panels!" Height="285" Width="325">
<!-- Ack! Two direct child elements of the <Window>!
<Label Name="lblInstructions"
Width="328" Height="27" FontSize="15">Enter Car Information</Label> <Button Name="btnOK" Height = "100" Width="80">OK</Button>
</Window>
Obviously a window that can only contain a single item is of little use. When a window needs to contain multiple elements, they must be arranged within any number of panels. The panel will contain all of the UI elements that represent the window, after which the panel itself is used as the object assigned to the Content property.
The Core Panel Types of WPF
The System.Windows.Controls namespace System.Windows.Controls namespace provides numerous panel types, each of which controls how subelements are positioned. Using panels, you can establish how the widgets behave when the end user resizes the window, if they remain exactly where placed at design time, if they reflow horizontally left to right or vertically top to bottom, and so forth.
To build complex user interfaces, panel controls can be intermixed (e.g., a DockPanel that contains a StackPanel) to provide for a great deal of flexibility and control. Furthermore, the panel types can work in conjunction with other document-centric controls (such as the ViewBox, TextBlock, TextFlow, and Paragraph types) to further customize how content is arranged within a given panel. Table 29-3 documents the role of some commonly used WPF panel controls.
Table 29-3. Core WPF Panel Controls
Panel Control |
Meaning in Life |
Canvas |
Provides a “classic” mode of content placement. Items stay exactly where you put |
|
them at design time. |
DockPanel |
Locks content to a specified side of the panel (Top, Bottom, Left, or Right). |
Grid |
Arranges content within a series of cells, maintained within a tabular grid. |

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1085 |
Panel Control |
Meaning in Life |
StackPanel |
Stacks content in a vertical or horizontal manner, as dictated by the Orientation |
|
property. |
WrapPanel |
Positions content from left to right, breaking the content to the next line at the |
|
edge of the containing box. Subsequent ordering happens sequentially from top |
|
to bottom or from right to left, depending on the value of the Orientation |
|
property. |
|
|
To illustrate the use of these commonly used panel types, in the next sections we’ll build the UI shown in Figure 29-16 within various panels and observe how the positioning changes when the window is resized.
Figure 29-16. Our target UI layout
Positioning Content Within Canvas Panels
Far and away, the simplest panel is Canvas. Most likely, Canvas is the panel you will feel most at home with, as it emulates the default layout of a Windows Forms application. Simply put, a Canvas panel allows for absolute positioning of UI content. If the end user resizes the window to an area that is smaller than the layout maintained by the Canvas panel, the internal content will not be visible until the container is stretched to a size equal to or larger than the Canvas area.
To add content to a Canvas, define the required subelements within the scope of the opening <Canvas> and closing </Canvas> tags and specify the location where rendering should occur (note that the content position can be relative to the left/right or top/bottom of the Canvas, but not both). If you wish to have the Canvas stretch over the entire surface of the container, simply omit the Height and Width properties. Consider the following XAML markup, which defines the layout shown in Figure 29-16:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Fun with Panels!" Height="285" Width="325">
<Canvas Background="LightSteelBlue">
<Button Canvas.Left="212" Canvas.Top="203" Name="btnOK" Width="80">OK</Button>
<Label Canvas.Left="17" Canvas.Top="14" Name="lblInstructions"
Width="328" Height="27" FontSize="15">Enter Car Information</Label>
<Label Canvas.Left="17" Canvas.Top="60" Name="lblMake">Make</Label>

1086 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
<TextBox Canvas.Left="94" Canvas.Top="60" Name="txtMake" Width="193" Height="25"/>
<Label Canvas.Left="17" Canvas.Top="109" Name="lblColor">Color</Label> <TextBox Canvas.Left="94" Canvas.Top="107" Name="txtColor"
Width="193" Height="25"/>
<Label Canvas.Left="17" Canvas.Top="155" Name="lblPetName">Pet Name</Label> <TextBox Canvas.Left="94" Canvas.Top="153" Name="txtPetName"
Width="193" Height="25"/>
</Canvas>
</Window>
In this example, each item within the <Canvas> scope is qualified by a Canvas.Left and Canvas. Top value, which control the content’s top-left positioning within the panel, using attached property syntax (see Chapter 28). As you may have gathered, vertical positioning is controlled using the Top or Bottom property, while horizontal positioning is established using Left or Right.
Given that each widget has been placed within the <Canvas> element, we find that as the window is resized, widgets are covered up if the container’s surface area is smaller than the content (see Figure 29-17).
Figure 29-17. Content in a Canvas panel allows for absolute positioning.
The order you declare content within a Canvas is not used to calculate placement, as this is based on the control’s size and the Canvas.Top, Canvas.Bottom, Canvas.Left, and Canvas.Right properties. Given this, the following markup (which groups together like-minded controls) results in an identical rendering:
<Canvas Background="LightSteelBlue">
<TextBox Canvas.Left="94" Canvas.Top="153" Name="txtColor"
Width="193" Height="25"/>
<TextBox Canvas.Left="94" Canvas.Top="60" Name="txtPetName"
Width="193" Height="25"/>
<TextBox Canvas.Left="94" Canvas.Top="107" Name="txtMake"
Width="193" Height="25"/>
<Label Canvas.Left="17" Canvas.Top="14" Name="lblInstructions"
Width="328" Height="27" FontSize="15">Enter Car Information</Label>
<Label Canvas.Left="17" Canvas.Top="109" Name="lblColor">Color</Label>
<Label Canvas.Left="17" Canvas.Top="155" Name="lblMake">Pet Name</Label>
<Label Canvas.Left="17" Canvas.Top="60" Name="lblPetName">Make</Label>
<Button Canvas.Left="212" Canvas.Top="203" Name="btnOK" Width="80">OK</Button> </Canvas>


1088 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
By default, content within a WrapPanel flows left to right. However, if you change the value of the Orientation property to Vertical, you can have content wrap in a top-to-bottom manner:
<WrapPanel Background="LightSteelBlue" Orientation ="Vertical">
A WrapPanel (as well as some other panel types) may be declared by specifying ItemWidth and ItemHeight values, which control the default size of each item. If a subelement does provide its own Height and/or Width value, it will be positioned relative to the size established by the panel. Consider the following markup:
<WrapPanel Background="LightSteelBlue" ItemWidth ="200" ItemHeight ="30"> <Label Name="lblInstruction"
FontSize="15">Enter Car Information</Label> <Label Name="lblMake">Make</Label>
<TextBox Name="txtMake"/>
<Label Name="lblColor">Color</Label> <TextBox Name="txtColor"/>
<Label Name="lblPetName">Pet Name</Label> <TextBox Name="txtPetName"/>
<Button Name="btnOK" Width ="80">OK</Button> </WrapPanel>
When rendered, we find the output shown in Figure 29-19 (notice the size and position of the Button widget).
Figure 29-19. A WrapPanel can establish the width and height of a given item.
As you might agree after looking at Figure 29-19, a WrapPanel is not typically the best choice for arranging content directly in a window, as the elements can become scrambled as the user resizes the window. In most cases, a WrapPanel will be a subelement to another panel type, to allow a small area of the window to wrap its content when resized.
■Source Code The SimpleWrapPanel.xaml file can be found under the Chapter 29 subdirectory.
Positioning Content Within StackPanel Panels
Like a WrapPanel, a StackPanel control arranges content into a single line that can be oriented horizontally or vertically (the default), based on the value assigned to the Orientation property. The difference, however, is that the StackPanel will not attempt to wrap the content as the user resizes the window. Rather, the items in the StackPanel will simply stretch (based on their orientation) to accommodate the size of the StackPanel itself. For example, the following markup results in the output shown in Figure 29-20:


1090 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
Positioning Content Within Grid Panels
Of all the panels provided with the WPF APIs, Grid is far and away the most flexible. Like an HTML table, the Grid can be carved up into a set of cells, each one of which provides content. When defining a Grid, you perform three steps:
1.Define and configure each column.
2.Define and configure each row.
3.Assign content to each cell of the grid using attached property syntax.
■Note If you do not define any rows or columns, the <Grid> defaults to a single cell that fills the entire surface of the window. Furthermore, if you do not assign a cell value for a subelement within a <Grid>, it automatically attaches to column 0, row 0.
The first two steps (defining the columns and rows) are achieved by using the <Grid. ColumnDefinitions> and <Grid.RowDefinitions> elements, which contain a collection of
<ColumnDefinition> and <RowDefinition> elements, respectively. Because each cell within a grid is indeed a true .NET type, you can configure the look and feel and behavior of each item as you see fit. Here is a rather simple <Grid> definition that arranges our UI content as shown in Figure 29-22:
<Grid ShowGridLines ="True" Background ="AliceBlue">
<! |
-- Define the rows/columns -- |
> |
<Grid.ColumnDefinitions> |
|
|
|
<ColumnDefinition/> |
|
|
<ColumnDefinition/> |
|
</Grid.ColumnDefinitions> |
|
|
<Grid.RowDefinitions> |
|
|
|
<RowDefinition/> |
|
|
<RowDefinition/> |
|
</Grid.RowDefinitions> |
|
<!-- Now add the elements to the grid's cells-->
<Label Name="lblInstruction" Grid.Column ="0" Grid.Row ="0" FontSize="15">Enter Car Information</Label>
<Button Name="btnOK" Height ="30" Grid.Column ="0" Grid.Row ="0" >OK</Button> <Label Name="lblMake" Grid.Column ="1" Grid.Row ="0">Make</Label>
<TextBox Name="txtMake" Grid.Column ="1" Grid.Row ="0" Width="193" Height="25"/> <Label Name="lblColor" Grid.Column ="0" Grid.Row ="1" >Color</Label>
<TextBox Name="txtColor" Width="193" Height="25" Grid.Column ="0" Grid.Row ="1" />
<!-- |
Just to keep things |
interesting, add |
some |
color to the pet |
name cell -- |
> |
||
<Rectangle Fill ="LightGreen" Grid.Column |
="1" |
Grid.Row |
="1" |
/> |
|
|
||
<Label Name="lblPetName" |
Grid.Column ="1" |
Grid.Row ="1" |
>Pet |
Name</Label> |
|
<TextBox Name="txtPetName" Grid.Column ="1" Grid.Row ="1" Width="193" Height="25"/>
</Grid>
Notice that each element (including a light green Rectangle element, thrown in for good measure) connects itself to a cell in the grid using the Grid.Row and Grid.Column attached properties. By default, the ordering of cells in a grid begins at the upper left, which is specified via Grid.Column="0" Grid.Row="0". Given that our grid defines a total of four cells, the bottom-right cell can be identified via Grid.Column="1" Grid.Row="1".

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1091 |
Figure 29-22. The Grid panel in action
■Source Code The SimpleGrid.xaml file can be found under the Chapter 29 subdirectory.
Grids with GridSplitter Types
Grid types can also support splitters. As you most likely know, splitters allow the end user to resize rows or columns of a grid type. As this is done, the content within each resizable cell will reshape itself based on how the items have been contained. Adding splitters to a Grid is very easy to do; simply define the <GridSplitter> type, using attached property syntax to establish which row or column it affects. Do be aware that you must assign a Width or Height value (depending on vertical or horizontal splitting) in order to be visible on the screen. Consider the following simple Grid type with a splitter on the first column (Grid.Column = "0"):
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="FunWithPanels" Height="191" Width="436">
<Grid Background ="AliceBlue">
<!-- Define columns -->
<Grid.ColumnDefinitions> <ColumnDefinition Width ="Auto"/> <ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Add this label to cell 0 -->
<Label Name="lblLeft" Background ="GreenYellow"
|
Grid.Column="0" |
Content ="Left!"/> |
|
<!-- |
Define the splitter -- |
> |
|
<GridSplitter Grid.Column ="0" Width ="5"/>
<!-- |
Add this label to cell 1 -- |
> |
<Label Name="lblRight" Grid.Column ="1" Content ="Right!"/> </Grid>
</Window>
First and foremost, notice that the column that will support the splitter has a Width property of Auto. Next, notice that the <GridSplitter> makes use of attached property syntax to establish which column it is working with. If you were to view this output, you would find a 5-pixel splitter that allows you to resize each Label (because we have not specified Height or Width properties for either Label, they fill up the entire cell). See Figure 29-23.