
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf


1074 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
to assign any object to function as the header (a simple string, a colored rectangle, a button, etc.). Consider the following two GroupBox declarations, which frame the previous RadioButtons in various manners:
<StackPanel>
<GroupBox Header = "Select Your Music Media" BorderBrush ="Black">
<StackPanel>
<RadioButton GroupName = "Music" >CD Player</RadioButton> <RadioButton GroupName = "Music" >MP3 Player</RadioButton> <RadioButton GroupName = "Music" >8-Track</RadioButton>
</StackPanel>
</GroupBox>
<GroupBox BorderBrush ="Black">
<GroupBox.Header>
<Label Background = "Blue" Foreground = "White"
FontSize = "15" Content = "Select your color choice"/> </GroupBox.Header>
<StackPanel>
<RadioButton>Red</RadioButton>
<RadioButton>Green</RadioButton>
<RadioButton>Blue</RadioButton>
</StackPanel>
</GroupBox>
</StackPanel>
The output can be seen in Figure 29-8.
Figure 29-8. GroupBox types framing RadioButton types
Framing Related Elements in Expanders
In addition to the customary group box, WPF ships with a new UI element that can group a collection of UI elements that can be hidden or shown via a toggle. This element, the Expander type, allows you to define the direction elements will be displayed (up, down, left, or right) using the ExpandDirection property. Consider the following XAML (which basically just changes <GroupBox> to <Expander>):
<StackPanel>
<Expander Header = "Select Your Music Media" BorderBrush ="Black">
<StackPanel>
<RadioButton GroupName = "Music" >CD Player</RadioButton> <RadioButton GroupName = "Music" >MP3 Player</RadioButton> <RadioButton GroupName = "Music" >8-Track</RadioButton>
</StackPanel>
</Expander>
<Expander BorderBrush ="Black">
<Expander.Header>
<Label Background = "Blue" Foreground = "White"

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1075 |
FontSize = "15" Content = "Select your color choice"/> </Expander.Header>
<StackPanel>
<RadioButton>Red</RadioButton>
<RadioButton>Green</RadioButton>
<RadioButton>Blue</RadioButton>
</StackPanel> </Expander >
</StackPanel>
Figure 29-9 shows each Expander in the collapsed state.
Figure 29-9. Collapsed Expanders
Figure 29-10 shows each Expander (pardon the redundancy) expanded.
Figure 29-10. Expanded Expanders
■Source Code The CheckRadioGroup.xaml file is included under the Chapter 29 subdirectory.
Working with the ListBox and ComboBox Types
As you would hope, WPF provides types that contain a group of selectable items, such as ListBox and ComboBox, both of which derive from the ItemsControl abstract base class. Most importantly, this parent class defines a property named Items, which returns a strongly typed ItemCollection object that holds onto the subitems. As it turns out, the ItemCollection type has been constructed to operate on System.Object types, and therefore it can contain anything whatsoever. If you wish to fill an ItemsControl-derived type with simply textual data via markup, you can do so using a set of <ListBoxItem> types. For example, consider the following XAML:
<!-- Simple list box -->
<ListBox Name = "lstVideoGameConsoles"> <ListBoxItem>Microsoft XBox 360</ListBoxItem> <ListBoxItem>Sony Playstation 3</ListBoxItem> <ListBoxItem>Nintendo Wii</ListBoxItem> <ListBoxItem>Sony PSP</ListBoxItem> <ListBoxItem>Nintendo DS</ListBoxItem>
</ListBox>

1076 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
<!-- Simple combo box -->
<ComboBox Name = "comboVideoGameConsoles"> <ListBoxItem>Microsoft XBox 360</ListBoxItem> <ListBoxItem>Sony Playstation 3</ListBoxItem> <ListBoxItem>Nintendo Wii</ListBoxItem> <ListBoxItem>Sony PSP</ListBoxItem> <ListBoxItem>Nintendo DS</ListBoxItem>
</ComboBox>
■Note ComboBox types can also be populated using <ComboBoxItem> elements, rather than <ListBoxItem>. By doing so, you gain access to the IsHighlighted property, which is not used by the ListBoxItem type.
Not surprisingly, we find the rendering shown in Figure 29-11.
Figure 29-11. A simple ListBox and ComboBox
Filling List Controls Programmatically
Oftentimes, the data contained within a list control is not known until runtime; for example, you may need to fill items in a list box based on values returned from a database read, invoking a WCF service, or reading an external file. When you need to populate a ListBox or ComboBox control programmatically, simply use the members of the ItemCollection type to do so (Add(), Remove(), etc.). Assume you have a new Visual Studio 2008 WPF Application project named ListControls. The previous XAML declaration of the lstVideoGameConsole type could be defined in XAML as follows:
<Window x:Class="ListControls.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ListControls" Height="300" Width="300" >
<StackPanel>
<!-- This is filled via code -->
<ListBox Name = "lstVideoGameConsoles"> </ListBox>
</StackPanel>
</Window>
and populated in a related code file as follows:
public partial class MainWindow : System.Windows.Window
{
public MainWindow()
{
InitializeComponent();

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1077 |
FillListBox();
}
private void FillListBox()
{
// Add items to the list box. lstVideoGameConsoles.Items.Add("Microsoft XBox 360"); lstVideoGameConsoles.Items.Add("Sony Playstation 3"); lstVideoGameConsoles.Items.Add("Nintendo Wii"); lstVideoGameConsoles.Items.Add("Sony PSP"); lstVideoGameConsoles.Items.Add("Nintendo DS");
}
}
One thing that might strike you as odd is that in the XAML description of the ListBox, we made use of <ListBoxItem> types to populate the items; however, here we have made use of string types when calling the Add() method. The short explanation is that when using XAML, <ListBoxItem> types are more convenient in that they are defined within the http://schemas.microsoft.com/ winfx/2006/xaml/presentation XML namespace, and therefore we have a direct reference to them.
Under the hood, ToString() is called on each <ListBoxItem> type, so the end result is identical. If you truly wanted to use a System.String to fill the ListBox (or ComboBox) type in XAML, you would need to define a new XML namespace to bring in mscorlib.dll (see Chapter 28 for more details):
<StackPanel xmlns:CorLib = "clr-namespace:System;assembly=mscorlib"> <ListBox Name = "lstVideoGameConsoles">
<CorLib:String>Microsoft XBox 360</CorLib:String> <CorLib:String>Sony Playstation 3</CorLib:String> <CorLib:String>Nintendo Wii</CorLib:String> <CorLib:String>Sony PSP</CorLib:String> <CorLib:String>Nintendo DS</CorLib:String>
</ListBox>
</StackPanel>
Conversely, if you really wanted to, you could programmatically populate an ItemsControl- derived type using strongly typed ListBoxItem objects; however, you really gain nothing for the current example and have in fact created additional work for yourself (as the ListBoxItem does not have a constructor to set the Content property!).
Adding Arbitrary Content
Because ListBox and ComboBox both have ContentControl in their inheritance chain, they can contain data well beyond a simple string. Consider the following ComboBox, which contains various <StackPanels> containing 2D graphical objects and a descriptive label:
<StackPanel>
<!-- A ListBox with content! -->
<ListBox Name = "lstColors"> <StackPanel Orientation ="Horizontal">
<Ellipse Fill ="Yellow" Height ="50" Width ="50"/>
<Label FontSize ="20" HorizontalAlignment="Center" VerticalAlignment="Center">Yellow</Label>
</StackPanel>
<StackPanel Orientation ="Horizontal">
<Ellipse Fill ="Blue" Height ="50" Width ="50"/>
<Label FontSize ="20" HorizontalAlignment="Center" VerticalAlignment="Center">Blue</Label>
</StackPanel>

1078 CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS
<StackPanel Orientation ="Horizontal">
<Ellipse Fill ="Green" Height ="50" Width ="50"/>
<Label FontSize ="20" HorizontalAlignment="Center" VerticalAlignment="Center">Green</Label>
</StackPanel>
</ListBox>
</StackPanel>
Figure 29-12 shows the output of our current list types.
Figure 29-12. ItemsControl-derived types can contain any sort of content you desire.
Determining the Current Selection
Once you have populated a ListBox or ComboBox type, the next obvious issue is how to determine at runtime which item the user has selected. As it turns out, you have three ways to do so. If you are interested in finding the numerical index of the item selected, you can use the SelectedIndex property (which is zero based; a value of -1 represents no selection). If you wish to obtain the object within the list that has been selected, the SelectedItem property fits the bill. Finally, the SelectedValue allows you to obtain the value of the selected object (typically obtained via a call to
ToString()).
Sounds simple enough, right? Well, to test how each property behaves, assume you have defined two new Button types for the current window, both of which handle the Click event:
<!-- Buttons to get the selected items -->
<Button Name ="btnGetGameSystem" Click ="btnGetGameSystem_Click"> Get Video Game System
</Button>
<Button Name ="btnGetColor" Click ="btnGetColor_Click"> Get Color
</Button>
The Click handler for btnGetGameSystem will obtain the values of the SelectedIndex,
SelectedItem, and SelectedValue properties of the lstVideoGameConsoles object and display them in a message box:

CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1079 |
protected void btnGetGameSystem_Click(object sender, RoutedEventArgs args)
{
string data = string.Empty;
data += string.Format("SelectedIndex = {0}\n", lstVideoGameConsoles.SelectedIndex);
data += string.Format("SelectedItem = {0}\n", lstVideoGameConsoles.SelectedItem);
data += string.Format("SelectedValue = {0}\n", lstVideoGameConsoles.SelectedValue);
MessageBox.Show(data, "Your Game Info");
}
If you were to select “Nintendo Wii” from the list of game consoles and click the related button, you would find the message box shown in Figure 29-13.
Figure 29-13. Finding a selected string
However, what about obtaining the selected color?
Determining the Current Selection for Nested Content
Assume the Click event handler for the btnGetColor Button has implemented btnGetColor_Click() to print out the current selection, index, and value of the lstColors ListBox object. Now, if you were to select the first item in the lstColors list box (and click the related button), you may be surprised to find the output shown in Figure 29-14.
Figure 29-14. Finding a selected . . . StackPanel?
The reason for this output is the fact that the lstColors object is maintaining three StackPanel objects, each of which contains nested content. Therefore, SelectedItem and SelectedValue are simply calling ToString() on the StackPanel type, which returns its fully qualified name.
While you would be able to simply figure out which item was selected using the numerical value returned from SelectedIndex, another approach is to drill into the StackPanel’s child


CHAPTER 29 ■ PROGRAMMING WITH WPF CONTROLS |
1081 |
Working with Text Areas
WPF ships with a number of UI elements that allow you to gather textual-based user input. The most primitive types would be TextBox and PasswordBox, which we will examine here using a new Visual Studio 2008 WPF Application named TextControls.
Working with the TextBox Type
Like other TextBox types you have used in the past, the WPF TextBox type can be configured to hold a single line of text (the default setting) or multiple lines of text if the AcceptReturn property is set to true. Information within a TextBox will always be treated as character data, and therefore the “content” is always a string type that can be set and retrieved using the Text property:
<TextBox Name ="txtData" Text = "Hello!" BorderBrush ="Blue" Width ="100"/>
One aspect of the WPF TextBox type that is very unique is that it has the built-in ability to check the spelling of the data entered within it by setting the SpellCheck.IsEnabled property to true. When you do so, you will notice that like Microsoft Office, misspelled words are underlined in a red squiggle. Even better, there is an underlying programming model that gives you access to the spellchecker engine, which allows you to get a list of suggestions for misspelled words.
Update your current window XAML definition to make use of a Label, TextBox, and Button as follows (notice this TextBox supports multiple lines of text and has enabled spell checking):
<Window x:Class="TextControls.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TextControls" Height="204" Width="292" >
<StackPanel>
<Label FontSize ="15">Is this word spelled correctly?</Label> <TextBox SpellCheck.IsEnabled ="True" AcceptsReturn ="True"
Name ="txtData" FontSize ="12" BorderBrush ="Blue" Height ="100">
</TextBox>
<Button Name ="btnOK" Content ="Get Selections" Width = "100" Click ="btnOK_Click"/>
</StackPanel>
</Window>
With just this much functionality, you will already notice that when you type misspelled words into your TextBox, errors are marked as such. To complete our simple spell checker, update the Click event handler for the Button type as follows:
protected void btnOK_Click(object sender, RoutedEventArgs args)
{
string spellingHints = string.Empty;
// Try to get a spelling error at the current caret location.
SpellingError error = txtData.GetSpellingError(txtData.CaretIndex); if (error != null)
{
// Build a string of spelling suggestions. foreach (string s in error.Suggestions)
{
spellingHints += string.Format("{0}\n", s);