Chapter 3. Using Standard Controls

WPF makes it relatively easy for the average software developer to create rich and exciting user interfaces that integrate print-quality text, 2D and 3D graphics, animation, and multimedia content. However, for the foreseeable future, most business applications will still require a user interface constructed predominantly from the everyday Windows-style controls that users have become familiar with over the past 15 years.

Note

For those readers coming from a Windows Forms background, it will help to be able to know which WPF control is equivalent to each of the familiar Windows Forms controls. The .NET Framework documentation summarizes the equivalent controls at http://msdn2.microsoft.com/en-us/library/ms750559.aspx.

WPF provides a rich set of highly functional controls in the System.Windows.Controls namespace that meet the everyday needs of the business application developer. The recipes in this chapter focus on how to use these standard controls and describe how to do the following:

  • Display text (recipes 3-1 and 3-2)

  • Display static images (recipe 3-3)

  • Handle text input by a user (recipes 3-4, 3-5, and 3-6)

  • Validate and spell check text input by a user (recipes 3-7 and 3-8)

  • Handle and generate button clicks and set a default button for a form (recipes 3-9, 3-10, and 3-11)

  • Provide quick keyboard access to text buttons and boxes (recipes 3-12 and 3-13)

  • Get user input from a slider (recipe 3-14)

  • Display a context menu on a control (recipe 3-15)

  • Display and control the display properties of tool tips (recipes 3-16, 3-17, and 3-18)

  • Display and allow the user to select items from a set of radio buttons (recipe 3-19)

  • Display and allow the user to select items from a set of check boxes (recipe 3-20)

  • Display and allow the user to select items from a hierarchical tree (recipe 3-21)

  • Display and allow the user to select items from a list (recipe 3-22)

  • Change the content of a list dynamically (recipe 3-23)

  • Display and allow the user to select items from a combo box (recipe 3-24)

  • Display any control rotated from its default orientation (recipe 3-25)

Note

This chapter focuses on how to use the standard capabilities of the WPF controls found in the System.Windows.Controls namespace. All of these controls are highly customizable in terms of behavior and appearance; these advanced features will be discussed in Chapter 5 and Chapter 6.

Display Control Content Surrounded by Braces

Problem

You need to display text surrounded by braces (curly brackets) as the content of a control, as in {surrounded by braces}. Curly braces are used to denote markup extensions in XAML and are not normally valid in control content.

Solution

Place the content within the XAML element (where braces are permitted), or if you must use a content attribute, prefix the text with a pair of braces ({}).

How It Works

In XAML, braces identify the use of markup extension syntax within XAML control attributes. WPF will attempt to process any such string with a markup extension handler. On the occasions you need to surround the text within a control's content with braces, you must signal to WPF that the content is not markup extension syntax. You do this by prefixing the text with a pair of braces.

The Code

The following XAML demonstrates a Button, RadioButton, TextBox, and TextBlock (all from the System.Windows.Controls namespace) that display content surrounded by braces (see Figure 3-1):

<Window x:Class="Recipe_03_01.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 3_01" Height="200" Width="250">
     <StackPanel>
         <Button Content="{}{A Button}" Margin="10"
                Name="button1" Width="75" />
         <RadioButton Content="{}{A RadioButton}"
                HorizontalAlignment="Center" Margin="10" />
         <TextBox Text="{}{A TextBox}" HorizontalAlignment="Center"
                Margin="10" />
         <TextBlock HorizontalAlignment="Center" Margin="10"
                Text="{}{A TextBlock}" />
     </StackPanel>
</Window>
Control content surrounded by braces

Figure 3-1. Control content surrounded by braces

Display Simple Text

Problem

You need to display small amounts of simple text.

Solution

Use the System.Windows.Controls.TextBlock control.

How It Works

The TextBlock control provides a lightweight and easy-to-use way to include small amounts (typically up to one paragraph) of flow content in your UI. The properties of the TextBlock provide extensive control over the formatting of the contained text. Table 3-1 lists some of the more commonly used properties of the TextBlock control.

Table 3-1. Commonly Used Properties of the TextBlock Control

Property

Description

FontFamily

The name of the font family to apply to the text, for example, Tahoma or Arial. You can specify multiple font names separated by commas. The first font is the primary font, and the others are fallback fonts used only if the preceding fonts are not available.

FontSize

The size of the text expressed as a number and an optional unit identifier. By default, the unit is assumed to be px (pixels) but can also be in (inches), cm (centimeters), or pt (points).

FontStyle

The style to apply to the text; available styles include Italic, Normal, and Oblique (generally, oblique fonts are optically skewed but lack the individual letter forms and cursive accoutrements of true italics). See the System. Windows.FontStyles class for more information.

FontWeight

The weight to apply to the text; some of the available weights are Thin, Light, Normal, Medium, Bold, Heavy, and UltraBlack. See the System.Windows.FontWeight class for more information.

TextAlignment

The alignment of the text within the TextBlock control. Possible values are Left, Right, Center, and Justify.

TextDecoration

One or more comma-separated decoration styles to apply to the text. Possible values are Overline, Strikethrough, Baseline, and Underline. See the System.Windows.TextDecorations class for more information.

TextWrapping

The wrapping behavior of text within the TextBlock control. Possible values are WrapWithOverflow, NoWrap, and Wrap.

The Code

The following XAML demonstrates how to create the formatted TextBlock control shown in Figure 3-2:

<Window x:Class="Recipe_03_02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_02" Height="100" Width="300">
    <Grid>
        <TextBlock FontFamily="Tahoma, Arial"
                   FontSize="20"
                   FontStyle="Italic"
                   FontWeight="Light"
                   HorizontalAlignment="Center"
                   TextAlignment="Right"
                   TextDecorations="Underline, Strikethrough"
                   TextWrapping="Wrap"
                   VerticalAlignment="Center">
            The quick brown fox jumped over the lazy brown dog.
        </TextBlock>
    </Grid>
</Window>
A formatted TextBlock control

Figure 3-2. A formatted TextBlock control

Display a Static Image

Problem

You need to display a simple static (nonanimated) image on a form.

Solution

Use the Sytem.Windows.Controls.Image control, and specify the path to the image you want to display in the Image.Source property.

How It Works

The Image control provides an easy way to display static images and supports the following image types:

  • .bmp

  • .gif

  • .ico

  • .jpg

  • .png

  • .wdp

  • .tiff

To control the size of the image, you can use the Width or Height property of the Image control. You specify the size of the image as a number with an optional unit identifier. By default, the unit is assumed to be px (pixels) but can also be in (inches), cm (centimeters), or pt (points).

You should only ever set one, and not both, of Width or Heigth. WPF will determine the appropriate size for the unspecified property in order to keep the aspect ratio of the image unchanged. If you specify both properties, the image will likely appear stretched or squashed.

As a standard control, the Image class inherits many useful features from its parent classes, making it straightforward to control things such as the position, opacity, and tool tip associated with your image.

The Code

The following XAML demonstrates how to size and place images on a form using the Image control. It also demonstrates how easy it is to associate a tool tip (discussed in recipe 3-16) and an opacity setting with an image (see Figure 3-3).

<Window x:Class="Recipe_03_03.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_03" Height="300" Width="400">
    <StackPanel Orientation="Horizontal">
        <Image Margin="10" ToolTip="Bottom Image" Width="100"
               Source="ApressLogo.gif" />
        <Image Margin="-30" Opacity=".7" Source="ApressLogo.gif"
               ToolTip="Middle Image" Width="150" />
        <Image Source="ApressLogo.gif" ToolTip="Top Image"
               VerticalAlignment="Top" Width="150" />
    </StackPanel>
</Window>
Displaying images using the Image control

Figure 3-3. Displaying images using the Image control

Get Simple Text Input from a User

Problem

You need a simple way to allow a user to enter text.

Solution

Use the System.Windows.Controls.TextBox control.

How It Works

The easiest control to use for getting text input from a user is the TextBox control. The TextBox control supports only simple text formatting, so if you need more complex formatting, you should use the System.Windows.Controls.RichTextBox control (discussed in recipe 3-5).

Despite its limitations, the TextBox is a highly functional control that supports features such as multiline text entry, word wrap, scrolling, text selection, text alignment, cut/copy/paste, and drag and drop. Table 3-2 summarizes some of the more commonly used members of the TextBox control.

Table 3-2. Commonly Used Members of the TextBox Control

Member

Summary

Properties

AcceptsReturn

Controls whether the TextBox allows multiline text entry by controlling what happens when the user presses the Enter key. If set to False, the TextBox ignores the Enter key, resulting in a TextBox that takes only a single line of input.

AcceptsTab

Controls whether the user can insert Tab characters in the TextBox content or whether pressing Tab takes the user out of the TextBox and moves to the next control marked as a tab stop.

CaretIndex

Gets or sets the current insertion position index of the TextBox.

IsReadOnly

Controls whether the TextBox is read-only or whether the user can also edit the content of the TextBox. Even if IsReadOnly is set to True, you can still programmatically change the content of the TextBox.

LineCount

Gets the total number of lines in the TextBox.

MaxLength

Controls the maximum number of characters that the user can type into the TextBox. The default value of 0 (zero) means there is no limit.

SelectedText

Gets or sets the currently selected TextBox content.

Text

Gets or sets the content of the TextBox. Alternatively, place the desired text within the body of the XAML TextBox element.

TextAlignment

Controls the alignment of text in the TextBox. Possible values are Left, Right, Center, and Justify.

TextWrapping

Controls the word wrapping behavior of text in the TextBox. Possible values are WrapWithOverflow, NoWrap, and Wrap.

Methods

AppendText

Appends text to the existing content of the TextBox.

Clear

Clears all the contents of the TextBox.

Copy

Copies the currently selected TextBox content to the clipboard.

Cut

Cuts the currently selected TextBox content and places it in the clipboard.

Paste

Pastes the current content of the clipboard over the currently selected TextBox content or inserts it at the cursor position if nothing is selected.

Select

Selects a specified range of text in the TextBox control.

Member

Summary

SelectAll

Selects the entire content of the TextBox control.

Undo

Undoes the most recent undoable action on the TextBox control.

Event

TextChanged

The event fired when the text in a TextBox changes.

Note

The TextBox is not a lightweight control, containing anywhere from 10 to 30 individual visual elements depending on its configuration. This can become a performance problem if you try to display a large number of TextBox controls. If you only need to display and not edit text, you should use a System.Windows. Controls.TextBlock control (see recipe 3-2).

The Code

The following XAML demonstrates how to use a TextBox control that supports multiline input with word wrap and vertical scrolling. The buttons provide simple demonstrations of how to apply some of the functionality listed in Table 3-2.

<Window x:Class="Recipe_03_04.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_04" Height="300" Width="300">
    <StackPanel>
        <TextBox AcceptsReturn="True" Height="100" IsReadOnly="True"
                 Name="textBox1" TextAlignment="Left" TextWrapping="Wrap"
                 VerticalScrollBarVisibility="Auto">
            Default starting text.
        </TextBox>
        <WrapPanel Margin="10">
            <Button Margin="5" Name="textButton" Width="75"
                    Click="TextButton_Click">Set Text</Button>
            <Button Margin="5" Name="selectAllButton" Width="75"
                    Click="SelectAllButton_Click">Select All</Button>
            <Button Margin="5" Name="clearButton" Width="75"
                    Click="ClearButton_Click">Clear</Button>
            <Button Margin="5" Name="prependButton" Width="75"
                    Click="PrependButton_Click">Prepend</Button>
            <Button Margin="5" Name="insertButton" Width="75"
                    Click="InsertButton_Click">Insert</Button>
<Button Margin="5" Name="appendButton" Width="75"
                     Click="AppendButton_Click">Append</Button>
             <Button Margin="5" Name="cutButton" Width="75"
                     Click="CutButton_Click">Cut</Button>
             <Button Margin="5" Name="pasteButton" Width="75"
                     Click="PasteButton_Click">Paste</Button>
             <Button Margin="5" Name="undoButton" Width="75"
                     Click="UndoButton_Click">Undo</Button>
         <WrapPanel>
         <Grid>
             <Grid.ColumnDefinitions>
                 <ColumnDefinition/>
                 <ColumnDefinition/>
             </Grid.ColumnDefinitions>
             <RadioButton Checked="EditableChecked" Grid.Column="0"
                          HorizontalAlignment="Center" IsChecked="True"
                          Margin="5" Name="editableRadioButton" >
                 Editable</RadioButton>
             <RadioButton Checked="EditableChecked" Grid.Column="1"
                          HorizontalAlignment="Center" Margin="5"
                          Name="readonlyRadioButton">
                 Read Only</RadioButton>
         </Grid>
         <Grid>
             <Grid.ColumnDefinitions>
                 <ColumnDefinition/>
                 <ColumnDefinition/>
                 <ColumnDefinition/>
             </Grid.ColumnDefinitions>
             <RadioButton Checked="AlignmentChecked" Grid.Column="0"
                          HorizontalAlignment="Center" IsChecked="True"
                          Margin="5" Name="leftAlignRadioButton">
                 Left</RadioButton>
             <RadioButton Checked="AlignmentChecked" Grid.Column="1"
                          HorizontalAlignment="Center" Margin="5"
                          Name="centerAlignRadioButton">
                 Center</RadioButton>
             <RadioButton Checked="AlignmentChecked" Grid.Column="2"
                          HorizontalAlignment="Center" Margin="5"
                          Name="rightAlignRadioButton">
                 Right</RadioButton>
        </Grid>
    </StackPanel<
</Window>

The following code-behind handles the events fired by the buttons and radio buttons:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_04
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles the checking of the Text Alignment RadioButtons.
        private void AlignmentChecked(object sender, RoutedEventArgs e)
        {
            RadioButton button = e.OriginalSource as RadioButton;

            if (e.OriginalSource == leftAlignRadioButton)
            {
                textBox1.TextAlignment = TextAlignment.Left;
            }
            else if (e.OriginalSource == centerAlignRadioButton)
            {
                textBox1.TextAlignment = TextAlignment.Center;
            }
            else if (e.OriginalSource == rightAlignRadioButton)
            {
                textBox1.TextAlignment = TextAlignment.Right;
            }

            textBox1.Focus();
        }

        // Handles the click of the Append button. Adds text to the end
        // of the TextBox content.
        private void AppendButton_Click(object sender, RoutedEventArgs e)
        {
            textBox1.AppendText(" *** appended text ***");
        }
// Handles the click of the Clear button. Clears all content from
       // the TextBox.
       private void ClearButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Clear();
       }

       // Handles the click of the Cut button. Cuts the currently
       // selected text and places it in the clipboard.
       private void CutButton_Click(object sender, RoutedEventArgs e)
       {
           if (textBox1.SelectionLength == 0)
           {
               MessageBox.Show("Select text to cut first.", Title);
           }
           else
           {
               MessageBox.Show("Cut: " + textBox1.SelectedText, Title);

               textBox1.Cut();
           }
       }

       // Handles the checking of the Editable / ReadOnly RadioButtons.
       private void EditableChecked(object sender, RoutedEventArgs e)
       {
           RadioButton button = e.OriginalSource as RadioButton;

           if (e.OriginalSource == editableRadioButton)
           {
               textBox1.IsReadOnly = false;
           }
           else if (e.OriginalSource == readonlyRadioButton)
           {
               textBox1.IsReadOnly = true;
           }

           textBox1.Focus();
       }

       // Handles the click of the Insert button. Inserts text into
       // the TextBox at the current cursor location.
private void InsertButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Text = textBox1.Text.Insert(
               textBox1.CaretIndex, " *** inserted text *** ");
       }

       // Handles the click of the Paste button. Pastes the current
       // content of the clipboard into the TextBox at the current
       // cursor location.
       private void PasteButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Paste();
       }

       // Handles the click of the Prepend button. Adds text to the start
       // of the TextBox content.
       private void PrependButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Text =
               textBox1.Text.Insert(0, "*** Prepended text *** ");
       }

       // Handles the click of the Select All button. Selects all the
       // content in the TextBox.
       private void SelectAllButton_Click(object sender, RoutedEventArgs e)
       {

           textBox1.SelectAll();

           // Set the focus on the TextBox to make the selection visible.
           textBox1.Focus();
       }

       // Handles the click of the Set Text Button. Sets the content
       // of the TextBox to a default value.
       private void TextButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Text = "Replace default text with initial text value";
       }

       // Handles the click of the Undo Button. Undoes the last undoable
       // event.
       private void UndoButton_Click(object sender, RoutedEventArgs e)
       {
           textBox1.Undo();
       }
    }
}

Get Rich Text Input from a User

Problem

You need to allow the user to edit large amounts of text and give them fine-grained control over the formatting of text they enter.

Solution

Use the System.Windows.Controls.RichTextBox control.

How It Works

The RichTextBox is a sophisticated and highly functional control designed to allow you to display and edit System.Windows.Documents.FlowDocument objects. The combination of the RichTextBox and FlowDocument objects provides the user with access to advanced document-editing capabilities that you do not get in a System.Windows.Controls.TextBox control. These features include mixed text formatting, hyphenation, tables, lists, paragraphs, and embedded images.

To populate the content of a RichTextBox statically, you include a FlowDocument element as the content of the RichTextBox XAML declaration. Within the FlowDocument element, you can define richly formatted content using elements of the flow document content model. Key structural elements of this content model include Figure, Hyperlink, List, ListItem, Paragraph, Section, and Table.

To populate the RichTextBox in code, you must work with a FlowDocument object directly. You can either create a new FlowDocument object or obtain one currently in a RichTextBox through the RichTextBox.Document property.

You manipulate the content of the FlowDocument by selecting portions of its content using a System.Windows.Documents.TextSelection object. The TextSelection object contains two properties, Start and End, which identify the beginning and end positions of the FlowDocument content you want to manipulate. Once you have a suitable TextSelection object, you can manipulate its content using the TextSelection members.

Note

Chapter 7 contains more recipes using the RichTextBox control and FlowDocument object. For detailed information about flow content, see the .NET Framework documentation at http://msdn2.microsoft.com/en-us/library/ms753113.aspx.

To simplify the manipulation of FlowDocument objects, the RichTextBox supports standard commands defined by the ApplicationCommands and EditingCommands classes from the System.Windows.Input namespace. The RichTextBox also supports standard key combinations to execute basic text-formatting operations such as applying bold, italic, and underline formats to text as well as cutting, copying, and pasting selected content. Table 3-3 summarizes some of the more commonly used members of the RichTextBox control.

Table 3-3. Commonly Used Members of the RichTextBox Control

Member

Summary

Properties

AcceptsTab

Controls whether the user can insert Tab characters in the RichTextBox content or whether pressing Tab takes the user out of the RichTextBox and moves to the next control marked as a tab stop.

CaretPostion

Gets or sets the current insertion position index of the RichTextBox.

Document

Gets or sets the FlowDocument object that represents the RichTextBox content.

HorizontalScrollBarVisibility

Determines whether the RichTextBox displays a horizontal scrollbar.

IsReadOnly

Controls whether the RichTextBox is read-only or whether the user can also edit the content of the TextBox. Even if IsReadOnly is set to True, you can still programmatically change the content of the RichTextBox.

Selection

Gets a System.Windows.Documents.TextSelection object representing the current selection in the RichTextBox.

VerticalScrollBarVisibility

Determines whether the RichTextBox displays a vertical scrollbar.

Methods

AppendText

Appends text to the existing content of the RichTextBox.

Copy

Copies the currently selected RichTextBox content to the clipboard.

Cut

Cuts the currently selected RichTextBox content and places it in the clipboard.

Paste

Pastes the current content of the clipboard over the currently selected RichTextBox content or inserts it at the cursor position if nothing is selected.

SelectAll

Selects the entire content of the RichTextBox control.

Undo

Undoes the most recent undoable action on the RichTextBox control.

Event

TextChanged

The event fired when the text in a RichTextBox changes.

The Code

The following code provides a simple example of a RichTextBox used to edit a FlowDocument. The XAML defines a static FlowDocument that contains a variety of structural and formatting elements. The user interface provides a set of buttons to manipulate the RichTextBox content. The buttons rely on the application and editing command support provided by the RichTextBox control and use a style (discussed further in Chapter 6) to make the RichTextBox the target of the button's command.

<Window x:Class="Recipe_03_05.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_05" Height="350" Width="500">
    <DockPanel>
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
            <StackPanel.Resources>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="CommandTarget"
                            Value="{Binding ElementName=rtbTextBox1}" />
                </Style>
            </StackPanel.Resources>
            <Button Content="Clear" Name="btnClear" Click="btnClear_Click" />
            <Separator Margin="5"/>
            <Button Content="Cu_t" Command="ApplicationCommands.Cut" />
            <Button Content="_Copy" Command="ApplicationCommands.Copy" />
            <Button Content="_Paste" Command="ApplicationCommands.Paste" />
            <Separator Margin="5"/>
            <Button Content="_Undo" Command="ApplicationCommands.Undo" />
            <Button Content="_Redo" Command="ApplicationCommands.Redo" />
            <Separator Margin="5"/>
            <Button Content="_Bold" Command="EditingCommands.ToggleBold" />
            <Button Content="_Italic" Command="EditingCommands.ToggleItalic" />
            <Button Content="Underline"
                    Command="EditingCommands.ToggleUnderline" />
            <Separator Margin="5"/>
            <Button Content="_Right" Command="EditingCommands.AlignRight" />
            <Button Content="C_enter" Command="EditingCommands.AlignCenter" />
            <Button Content="_Left" Command="EditingCommands.AlignLeft" />
        </StackPanel>
        <RichTextBox DockPanel.Dock="Bottom" Name="rtbTextBox1"
                    HorizontalScrollBarVisibility="Visible"
                    VerticalScrollBarVisibility="Visible">
            <FlowDocument>
                <Paragraph FontSize="12">
                    Lorem ipsum dolor sit amet, consectetuer adipiscing elit,
                    sed diam nonummy nibh euismod tincidunt ut laoreet dolore
                    magna aliquam erat volutpat.
                </Paragraph>
                <Paragraph FontSize="15">
                    Ut wisi enim ad minim veniam, quis nostrud exerci tation
                    ullamcorper suscipit lobortis nisl ut aliquip ex ea
                    commodo consequat. Duis autem vel eum iriure.
                </Paragraph>

                <Paragraph FontSize="18">A List</Paragraph>
<List>
                    <ListItem>
                        <Paragraph>
                            <Bold>Bold List Item</Bold>
                        </Paragraph>
                    </ListItem>
                    <ListItem>
                        <Paragraph>
                            <Italic>Italic List Item</Italic>
                        </Paragraph>
                    </ListItem>
                    <ListItem>
                        <Paragraph>
                            <Underline>Underlined List Item</Underline>
                        </Paragraph>
                    </ListItem>
                </List>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</Window>

The following code-behind contains the event handler that handles the Clear button provided on the user interface defined earlier:

The following code-behind contains the event handler that handles the Clear button provided on the user interface defined earlier:

using System.Windows;

namespace Recipe_03_05

{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {

        public Window1()
        {
            InitializeComponent();
        }

        // Handles Clear Button click event.
        private void btnClear_Click(object sender, RoutedEventArgs e)
        {
            // Select all the text in the FlowDocument and cut it.
            rtbTextBox1.SelectAll();
            rtbTextBox1.Cut();
        }
     }
 }

Figure 3-4 shows what the RichTextBox looks like when the example is first run.

Using a RichTextBox to edit a FlowDocument

Figure 3-4. Using a RichTextBox to edit a FlowDocument

Load or Save the Content of a RichTextBox

Problem

You need to load the content of a System.Windows.Controls.RichTextBox from a file or save the current content to a file.

Solution

Load the content of the System.Windows.Documents.FlowDocument object contained in the RichTextBox.Document from a file using the XamlReader class. Save the content of the FlowDocument object using the XamlWriter class. Both the XamlReader and XamlWriter classes are members of the System.Windows.Markup namespace.

How It Works

The XamlReader and XamlWriter classes make it easy to serialize XAML to and from disk. Because you can represent a FlowDocument (the content model used within a RichTextBox) as XAML, the XamlReader and XamlWriter classes provide an excellent way to store and retrieve the content of a RichTextBox.

To load a FlowDocument stored in a XAML file into a RichTextBox, pass a System.IO.FileStream object representing the file to the static XamlReader.Load method. You must cast the returned System.Object to a FlowDocument and handle any formatting or casting errors that occur in the process. Once you have the FlowDocument, assign it to the Document property of the RichTextBox.

To write the FlowDocument content of a RichTextBox to a file, pass the FlowDocument from the RichTextBox.Document property and a FileStream object representing the destination file to the XamlWriter.Save method.

The Code

The following XAML displays a RichTextBox containing some default text along with the buttons necessary to create a new empty FlowDocument, open a FlowDocument, and save a FlowDocument:

<Window x:Class="Recipe_03_06.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_06" Height="300" Width="300">
    <DockPanel>
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
            <Button Content="_New" Name="btnNew" Click="btnNew_Click" />
            <Button Content="_Open" Name="btnOpen" Click="btnOpen_Click" />
            <Button Content="_Save" Name="btnSave" Click="btnSave_Click" />
        </StackPanel>
        <RichTextBox DockPanel.Dock="Bottom" Name="rtbTextBox1"
                     HorizontalScrollBarVisibility="Visible"
                     VerticalScrollBarVisibility="Visible">
            <FlowDocument>
                <Paragraph>
                    Lorem ipsum dolor sit amet, consectetuer adipiscing elit,
                    sed diam nonummy nibh euismod tincidunt ut laoreet dolore
                    magna aliquam erat volutpat.
                </Paragraph>
                <Paragraph>
                    Ut wisi enim ad minim veniam, quis nostrud exerci tation
                    ullamcorper suscipit lobortis nisl ut aliquip ex ea
                    commodo consequat. Duis autem vel eum iriure.
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</Window>

The following code-behind shows the event handlers for the New, Open, and Save buttons. The btnOpen_Click event handler uses the Microsoft.Win32.OpenFileDialog to provide a standard Windows dialog box to allow the user to select the file to open. Similarly, the btnSave_Click event handler uses the Microsoft.Win32.SaveFileDialog.

using Microsoft.Win32;
using System;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
namespace Recipe_03_06
{
     /// <summary>
     /// Interaction logic for Window1.xaml
     /// </summary>
     public partial class Window1 : Window
     {
         private String currentFileName = String.Empty;

         public Window1()
         {
             InitializeComponent();
         }

         // Handles the Open Button Click event
         private void btnOpen_Click(object sender, RoutedEventArgs e)
         {
             // Use a standard OpenFileDialog to allow the user to
             // select the file to open.
             OpenFileDialog dialog = new OpenFileDialog();
             dialog.FileName = currentFileName;
             dialog.Filter = "XAML Files (*.xaml)|*.xaml";

             // Display the OpenFileDialog and read if the user
             // provides a file name.
             if (dialog.ShowDialog() == true)
             {
                 // Remember the new file name.
                 currentFileName = dialog.FileName;

                 {
                     using (FileStream stream = File.Open(currentFileName,
                         FileMode.Open))
                     {
                         // TODO: Need logic to handle incorrect file format errors.
                         FlowDocument doc = XamlReader.Load(stream) as FlowDocument;

                         if (doc == null)
                         {
                             MessageBox.Show("Could not load document.", Title);
                         }
                         else
                         {
                            rtbTextBox1.Document = doc;
                         }
                     }
                 }
}
}

      // Handles the New Button Click event
      private void btnNew_Click(object sender, RoutedEventArgs e)
      {
          // Create a totally new FlowDocument for the RichTextBox.
          rtbTextBox1.Document = new FlowDocument();

          currentFileName = String.Empty;
      }

      // Handles the Save Button Click event
      private void btnSave_Click(object sender, RoutedEventArgs e)
      {
         // Use a standard SaveFileDialog to allow the user to
         // select the file to save.
         SaveFileDialog dialog = new SaveFileDialog();
         dialog.FileName = currentFileName;
         dialog.Filter = "XAML Files (*.xaml)|*.xaml";

         // Display the SaveFileDialog and save if the user
         // provides a file name.
         if (dialog.ShowDialog() == true)
         {
            // Remember the new file name.
            currentFileName = dialog.FileName;

            using (FileStream stream = File.Open(currentFileName,
               FileMode.Create))
            {
               XamlWriter.Save(rtbTextBox1.Document, stream);
            }
         }
      }
   }
}

Display a Password Entry Box

Problem

You need to allow the user to enter secret information (such as a password) and mask the characters entered so they cannot be read from the screen.

Solution

Use a System.Windows.Controls.PasswordBox control to get the user input.

How It Works

The PasswordBox control works like a simplified System.Windows.Controls.TextBox control. But for each character the user types, the PasswordBox control displays a placeholder symbol instead of the character entered. You can define the placeholder character by setting the PasswordChar property of the PasswordBox control; an asterisk (*) is the default.

In an extra effort to improve the security of the data entered by the user, the PasswordBox control stores its content internally in a System.Security.SecureString. This stores the string encrypted in memory, ensuring that if the memory is paged or written to disk as part of a memory dump, then the password remains secure. However, you can get the password from the PasswordBox via the Password property as a System.String, meaning you have to be very careful with the data if you want to maintain the same level of protection throughout your code.

The Code

The following example demonstrates how to use a PasswordBox to allow the user to enter a password, which it then displays when the user clicks the OK button (which is not very secure but demonstrates how to use the Password property). The example uses an exclamation mark as the password character.

<Window x:Class="Recipe_03_07.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_07" Height="100" Width="300">
    <StackPanel Orientation="Horizontal">
        <TextBlock Margin="5" VerticalAlignment="Center">
            Enter Password:
       </TextBlock>
       <PasswordBox Name="passwordBox" PasswordChar="!"
                    VerticalAlignment="Center" Width="150" />
       <Button Content="OK" IsDefault="True" Margin="5" Name="button1"
               VerticalAlignment="Center" Click="button1_Click" />
    </StackPanel>
</Window>

The following code-behind handles the button click event:

using System.Windows;

namespace Recipe_03_07
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles the Button Click event
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Password entered: " + passwordBox.Password,
                Title);
        }
    }
}

Figure 3-5 shows how the PasswordBox masks the user input with a selectable substitute character.

Using the PasswordBox to mask user input

Figure 3-5. Using the PasswordBox to mask user input

Spell Check a TextBox or RichTextBox Control in Real Time

Problem

You need to do a spell check as the user enters text into a System.Windows.Controls.TextBox or System.Windows.Controls.RichTextBox control.

Solution

Enable spell checking on the TextBox or RichTextBox control by setting the attached property SpellCheck.IsEnabled to True.

How It Works

The System.Windows.Controls.SpellCheck class provides real-time spell-checking functionality for text-editing controls. When enabled on a TextBox or RichTextBox control, as the user types text into the control, any unrecognized words are underlined in red. If the user right-clicks a highlighted word, they get a context menu, and they can select to replace the word from a set of suggested alternatives. The user can also choose to ignore the highlighted word, which will clear the highlighting of that word while the text entry control exists.

The SpellCheck control provides multilingual support. A TextBox control will use the language defined by the xml:lang attribute. For RichTextBox, the SpellCheck control determines which dictionary to use based on the current keyboard input language.

As of this writing, the SpellCheck control supports only US English (xml:lang="en-US"), UK English (xml:lang="en-GB"), French (xml:lang="fr-FR"), and German (xml:lang="de-DE"). You must have the appropriate language pack installed to enable multilingual dictionary support. Unfortunately, there is currently no way to create new dictionaries or to customize the content of the dictionaries provided.

The Code

The following XAML declares a TextBox with spell checking enabled:

<Window x:Class="Recipe_03_08.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 3_08" Height="100" Width="300">
     <StackPanel>
         <TextBlock FontSize="14" FontWeight="Bold"
                       Text="A spell-checking TextBox:"/>
         <TextBox AcceptsReturn="True" AcceptsTab="True" FontSize="14"
                     Margin="5" SpellCheck.IsEnabled="True" TextWrapping="Wrap">
                The qick red focks jumped over the lasy brown dog.
         </TextBox>
     </StackPanel>
</Window>

Figure 3-6 shows the spell-check-enabled TextBox from the previous example. It also shows the context menu displayed to allow the user to select suggested alternatives to the word identified as being misspelled.

Spell-check-enabled TextBox

Figure 3-6. Spell-check-enabled TextBox

Handle a Button Click

Problem

You need to display a button and take an action when the user clicks it.

Solution

Use the System.Windows.Controls.Button control, and handle its Click event in the code-behind.

How It Works

When the user clicks a Button control, WPF raises the Button control's Click event, which in turn invokes the method that is configured to handle the event. In XAML, the easiest way to register a method to handle a Click event is to specify the name of the handler method as the value of the Click attribute of the Button element.

Depending on what you are doing and the number of buttons you need to accommodate, you can create individual Click handler methods for each Button, or you can create a single method that determines which Button generated the Click event and takes the appropriate action.

To use the second approach, you can use the OriginalSource property of the System.Windows.RoutedEventArgs object that is passed to the Click event handler to determine which Button raised the event. The OriginalSource property contains a System.Object reference to the control that raised the event. You can compare the Object reference to see whether it is the same as a particular Button, or you can cast the Object reference to a Button and inspect its properties to determine which action to take.

Note

Chapter 12 contains recipes that provide more detail on the various mouse and keyboard input events and how to handle them appropriately.

The Code

The following example presents three Button controls. When the user clicks a Button, the example displays a System.Windows.MessageBox containing the name of the Button selected. This example casts the Object in the RoutedEventArgs.OriginalSource property to a Button to access the Name of the Button. Recipe 3-4 contains an example that directly compares a known Button control with the Object in the OriginalSource property.

<Window x:Class="Recipe_03_09.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_09" Height="200" Width="200">
    <StackPanel>
        <Button Click="SharedButtonClickHandler" Height="23" Margin="10"
                Name="button1" Width="75">Button One</Button>
        <Button Click="SharedButtonClickHandler" Height="23" Margin="10"
                Name="button2" Width="75">Button Two</Button>
        <Button Click="SharedButtonClickHandler" Height="23" Margin="10"
                Name="button3" Width="75">Button Three</Button>
    </StackPanel>
</Window>

Here is the code-behind containing the shared Click event handler:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_09
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
private void SharedButtonClickHandler(object sender,
             RoutedEventArgs e)
         {
             Button source = e.OriginalSource as Button;

             if (source != null)
             {
                 MessageBox.Show("You pressed " + source.Name, Title);
             }
         }
     }
}

Generate Click Events Repeatedly While a Button Is Clicked

Problem

You need to repeatedly generate click events for as long as a user "holds down" a button.

Solution

Use the System.Windows.Controls.Primitives.RepeatButton control.

How It Works

A standard System.Windows.Controls.Button control raises click events when the user clicks, releases, or hovers over the button (controlled by the Button.ClickMode property). The RepeatButton control provides an easy way to repeatedly raise Click events for the entire duration during which the user "holds down" a button.

Two properties control the timing of the Click events raised by the RepeatButton. The Delay property defines (in milliseconds) the delay from when the user first activates the RepeatButton to when it raises the first Click event. The default Delay value is the same as that of the keyboard delay obtained via the System.Windows.SystemParameters.KeyboardDelay property. The Interval property defines (in milliseconds) the time between subsequent Click events (as long as the user is still holding down the RepeatButton). The default Interval value is the same as that of the keyboard speed obtained via the System.Windows.SystemParameters.KeyboardSpeed property.

The Code

The following example uses two RepeatButton controls to move a slider. The first RepeatButton must be clicked and held to move the slider to the left. The second RepeatButton moves the slider to the right while the mouse hovers over the xbutton.

<Window x:Class="Recipe_03_10.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_10" Height="100" Width="200">
    <StackPanel>
        <Slider Name="slider" Maximum="100" Minimum="0" Value="50" />
        <StackPanel Orientation="Horizontal">
            <RepeatButton Click="SliderLeft_Click" Content="Click Me"
                          Height="23" Margin="10" Width="70"
                          ToolTip="Click to move slider left" />
            <RepeatButton Click="SliderRight_Click" ClickMode="Hover"
                          Content="Touch Me" Height="23" Margin="10"
                          ToolTip="Hover to move slider right" Width="70" />
        </StackPanel>
    </StackPanel>
</Window>

The following code-behind shows how to handle the Click events raised by the RepeatButton controls:

using System.Windows;

namespace Recipe_03_10
{
     /// <summary>
     /// Interaction logic for Window1.xaml
     /// </summary>
     public partial class Window1 : Window
     {
         public Window1()
         {
             InitializeComponent();
         }

         // Handles the SliderLeft Click event.
         private void SliderLeft_Click(object sender, RoutedEventArgs e)
         {
             // Reduce the value of the slider by one for each click.
             slider.Value -= 1;
         }

         // Handles the SliderRight Click event.
         private void SliderRight_Click(object sender, RoutedEventArgs e)
         {
             // Increase the value of the slider by one for each click.
             slider.Value += 1;
         }
     }
}

Set a Default Button

Problem

You need to identify one default button from a group of buttons that is "clicked" when the user presses Enter.

Solution

Set the IsDefault property of the System.Windows.Controls.Button control to True.

How It Works

It is standard behavior in a data entry form that the currently selected button is "clicked" when the user presses the Enter key. But when the user is active in a nonbutton control such as a text entry control or a list, users expect forms to have a default button that is "clicked" when they press Enter. Usually, this will be the OK, Next, Send, or Submit button.

By default, the currently selected Button (as you tab through them, for example) has a colored border identifying that it is the Button that will be clicked if you press Enter. By setting the IsDefault property of a Button control to True, you identify that Button as the default. Whenever the user is focused on a nonbutton control, the default Button will have a colored border to indicate that it will be "clicked" if the user presses Enter.

If you configure two default Button controls, when the user presses Enter, focus jumps to the first default Button in the tab order (making it active), but it is not clicked automatically.

The Code

The following example contains a System.Windows.Controls.TextBox and three Button controls. The button3 Button is configured as the default. As the default, btnThree is selected when the application first loads and is "clicked" when you press Enter while editing the TextBox.

<Window x:Class="Recipe_03_11.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_11" Height="100" Width="300">
    <DockPanel>
        <TextBox DockPanel.Dock="Top" Margin="5">
            Button three is the default button.
        </TextBox>
        <StackPanel HorizontalAlignment="Center" DockPanel.Dock="Bottom"
                    Orientation="Horizontal">
            <Button Click="SharedButtonClickHandler" Content="Button One"
                    Margin="5" Name="btnOne" Width="75" />
            <Button Click="SharedButtonClickHandler" Content="Button Two"
                    Margin="5" Name="btnTwo" Width="75" />
<Button Click="SharedButtonClickHandler" Content="Button Three"
                        IsDefault="True" Margin="5" Name="btnThree" />
        </StackPanel>
    </DockPanel>
</Window>

Here is the code-behind containing the shared Click event handler:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_11
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles the click event for all buttons.
        private void SharedButtonClickHandler(object sender,
            RoutedEventArgs e)
        {
            Button source = e.OriginalSource as Button;

            if (source != null)
            {
                MessageBox.Show("You pressed " + source.Name, Title);
            }
        }
    }
}

Figure 3-7 shows how a default button is identified when the user is focused on a text entry control.

Setting a default Button

Figure 3-7. Setting a default Button

Provide Quick Keyboard Access to Text Boxes

Problem

You need to provide a keyboard shortcut so that users can jump to specific System.Windows.Controls.TextBox controls.

Solution

Create a label for the TextBox using the System.Windows.Controls.Label control. Define access keys within the content of the Label control by preceding the desired access character with an underscore (_). Then use the Target property of the Label control to identify the TextBox that is the intended target when the user presses the access key.

How It Works

The TextBox is one of the most common controls users want to access quickly from the keyboard (another being buttons, which are discussed in recipe 3-13). However, the TextBox itself has no constant text in which you can embed an access key. Using a Label control, you can specify an access key in the content of the Label and then identify a TextBox target for the access key.

To define an access key in a Label, precede the letter you want to be the access key with an underscore (_). By default, the first underscore identifies the access key, so if you need to use underscores in the Label prior to the desired access key, use double underscores (__) so that WPF doesn't treat it as an access key and instead displays a normal underscore.

When a user presses the access key for the Label (while pressing Alt), instead of jumping to the Label, the user's focus is redirected to the targeted TextBox.

The Code

The following XAML displays a window containing three Label and TextBox control pairs. Each Label defines a numeric quick access key (1, 2, or 3) that, when pressed in conjunction with the Alt key, takes you to the TextBox associated with the Label. In Windows Vista, the default configuration displays only the underscore when you press the Alt key.

<Window x:Class="Recipe_03_12.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_12" Height="200" Width="250">
    <StackPanel>
        <StackPanel Margin="10" Orientation="Horizontal">
            <Label Target="{Binding ElementName=textBox1}">Label _1</Label>
            <TextBox Name="textBox1" Width="150"></TextBox>
        </StackPanel>
<StackPanel Margin="10" Orientation="Horizonal">
            <Label Target="{Binding ElementName=textBox2}">Label_2</Label>
            <TextBox Name="textBox2" Width="150"></TextBox>
        </StackPanel>
        <StackPanel Margin="10" Orientation="Horizontal">
            <Label Targe="{Binding ElementName=textBox3}">Label_3</Label>
            <TextBox Name="textBox3" Width="150"></TextBox>
        </StackPanel>
    <StackPanel>
</Window>

Provide Quick Keyboard Access to Buttons

Problem

You need to provide a keyboard shortcut so that users can quickly "click" a System.Windows.Controls.Button control without using the mouse.

Solution

In the Content property of the Button, precede the letter you want to be the access key with an underscore (_).

How It Works

The first underscore character in the Content property of a Button control identifies the access key for that Button. If the user presses one of the available access keys while holding the Alt key, WPF raises the Click event on the Button as if the user had clicked the Button with the mouse.

Because the first underscore identifies the access key, if you need to use underscores in the content prior to the desired access key, use double underscores (__) so that WPF doesn't treat it as an access key and instead displays a normal underscore.

Note

Even though the access key underscore is not displayed onscreen as a normal part of the Button content, it is still there. You must take this into consideration if you need to work with the Button content programmatically.

The Code

The following XAML demonstrates how to use underscores to define access keys for the three Button controls:

<Window x:Class="Recipe_03_13.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_13" Height="100" Width="300">
    <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
        <Button Click="SharedButtonClickHandler" Height="23" Margin="5"
                 Name="button1" Width="75">Button _One</Button>
        <Button Click="SharedButtonClickHandler" Height="23" Margin="5"
                 Name="button2" Width="75">Button _Two</Button>
        <Button Click="SharedButtonClickHandler" Height="23" Margin="5"
                 Name="button3" Width="75">Button T_hree</Button>
    </StackPanel>
</Window>

The following code-behind provides the Click event handler for the preceding XAML:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_13
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void SharedButtonClickHandler(object sender, RoutedEventArgs e)
        {
            Button source = e.OriginalSource as Button;

            if (source != null)
            {
                string message = String.Format("{0} was pressed.", source.Content);
                MessageBox.Show(message, Title);
            }
        }
    }
}

Get User Input from a Slider

Problem

You need to allow the user to provide input to your application using a slider.

Solution

Use the System.Windows.Controls.Slider control. You can obtain the current value of the Slider from its Value property or handle the ValueChanged event to respond dynamically as the Slider value changes.

How It Works

A Slider control allows a user to choose one value from a range of values by moving a thumb along a track. You set the values for the extremes of the track using the Minimum and Maximum properties of the Slider control and get or set the current position of the thumb using the Value property.

Other properties of the Slider control allow you to control the frequency and location of tick marks that divide the length of the track and how the thumb moves along the track in response to user interaction. Table 3-4 summarizes some of the most commonly used properties of the Slider control.

Table 3-4. Commonly Used Properties of the Slider Control

Property

Description

IsSnapToTickEnabled

Determines whether the Slider automatically moves the thumb to the closest tick mark.

LargeChange

The size of the change in the Value property when the Slider control receives an IncreaseLarge or DecreaseLarge command. By default, this occurs when the user clicks the central track of the slider to either side of the thumb.

Maximum

The maximum value the Slider.Value can contain when the user moves the thumb all the way to the right.

Minimum

The minimum value the Slider.Value can contain when the user moves the thumb all the way to the left.

SmallChange

The size of the change in the Value property when the Slider control receives an IncreaseSmall or DecreaseSmall command. By default, this occurs when the user presses the left or right arrow keys when the thumb of the Slider is selected.

TickFrequency

The interval between ticks along the Slider track. The TickPlacement property must have a value other than None for the tick marks to be visible.

TickPlacement

The location of the tick marks relative to the Slider track. Possible values are None, TopLeft, BottomRight, or Both. Also, changes the style of the thumb to point more precisely at the ticks if the value is TopLeft or BottomRight.

Ticks

A set of specific tick marks to show along the length of the Slider expressed as a comma-separated list of values. Values outside the range defined by the Minimum and Maximum properties are ignored. The TickPlacement property must have a value other than None for the tick marks to be visible.

Value

The current value of the Slider, which defines the position of the thumb along the Slider control's track. Setting Value less than the Minimum property defaults to the value of Minimum, and setting Value greater than the Maximum property defaults to the value of Maximum.

The Code

The following XAML displays two Slider controls. The top Slider allows the user to move the thumb freely between the Minimum and Maximum values but specifies a LargerChange value of 10 for when the user clicks the track. The bottom Slider uses a TickFrequency value of 25 and forces the thumb to align to a tick using the IsSnapToTickEnabled property. This results in a slider that always moves in increments of 25.

<Window x:Class="Recipe_03_14.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_14" Height="200" Width="300">
    <StackPanel>
        <TextBlock Margin="5" Text="0" FontSize="20"
                   HorizontalAlignment="Center" Name="txtSliderValue" />
        <Slider LargeChange="10" Margin="5" Maximum="1000" Minimum="0"
                Name="slider1" TickPlacement="TopLeft"
                Ticks="100, 200, 400, 800" Value="0"
                ValueChanged="slider_ValueChanged" />
        <Button Name="btnGetSliderValue1" Width="100"
                Click="GetSliderValue_Click">Get Slider 1 Value</Button>
        <Slider IsSnapToTickEnabled="True" Margin="5" Maximum="1000"
                Minimum="0" Name="slider2" TickFrequency="25"
                TickPlacement="BottomRight" Value="1000"
                ValueChanged="slider_ValueChanged" />
        <Button Name="btnGetSliderValue2" Width="100"
                Click="GetSliderValue_Click">Get Slider 2 Value</Button>
    </StackPanel>
</Window>

The following code-behind demonstrates how to obtain the current Value of a Slider in response to a button click and also shows how to handle the ValueChanged event to respond dynamically to the movement of the Slider thumb:

using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_14
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles all GetSliderValue Button Clicks
        private void GetSliderValue_Click(object sender, RoutedEventArgs e)
        {
            Button button = e.OriginalSource as Button;
            string message = "Unknown slider.";

            if (button == btnGetSliderValue1)
            {
                message = "Slider1 value = " + slider1.Value;
            }
            else if (button == btnGetSliderValue2)
            {
            message = "Slider2 value = " + slider2.Value;
        }

        MessageBox.Show(message, Title);
     }

     // Handles all Slider ValueChangedEvents.
     private void slider_ValueChanged(object sender,
         RoutedPropertyChangedEventArgs<double> e)
     {
         Slider slider = e.OriginalSource as Slider;

         if (slider != null)
         {
             txtSliderValue.Text = slider.Value.ToString();
         }
      }
   }
}

Figure 3-8 shows the sample code where the number at the top of the window shows the Value property of the top slider.

Getting user input from a Slider

Figure 3-8. Getting user input from a Slider

Display a Context Menu

Problem

You need to show a context menu when a user right-clicks a control.

Solution

Set the ContextMenu property of the control, and configure the context menu using a hierarchy of System.Windows.Controls.MenuItem objects to define each menu option.

How It Works

You attach a context menu to an element by setting that element's ContextMenu property. With the ContextMenu property defined, right-clicking the control will bring up the context menu.

You define the structure and content of the context menu by creating a hierarchy of MenuItem objects. The Header property of the MenuItem defines the name displayed for the menu option. MenuItem supports the basic formatting of the Header, including things such as the font family, size, style, and weight. You can also define access keys by prefixing the appropriate character in the Header with an underscore (_).

To define the functionality of the ContextMenu items, you can either define Click event handlers on each MenuItem or define Command bindings. If the control the ContextMenu is attached to is aware of the bound Command, then you do not need to implement special logic because WPF passes the Command to the parent control when the user clicks the MenuItem.

Note

Chapter 4 contains more examples of using commands and command bindings.

The ContextMenu also supports properties that make it easy to create rich visual effects such as HasDropShadow to turn on a shadow behind the ContextMenu and Opacity to control the opacity of the ContextMenu.

The Code

The following XAML demonstrates how to define a ContextMenu for a TextBox that lets the user clear, select, cut, copy, and paste content from the TextBox. A submenu allows the user to change the format of the TextBox content. The example uses a shared Click event handler to handle format changes, individual Click event handlers for the Clear and Select All menu items, and standard Command bindings for the Cut, Copy, and Paste commands, which the TextBox knows how to handle.

<Window x:Class="Recipe_03_15.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_15" Height="100" Width="300">
    <Grid>
        <TextBox FontSize="16" Height="23" Name="txtTextBox" >
            <TextBox.ContextMenu>
                <ContextMenu HasDropShadow="True" Opacity=".8">
                    <MenuItem Command="Cut" Header="Cu_t" />
                    <MenuItem Command="Copy" Header="_Copy" />
                    <MenuItem Command="Paste" Header="_Paste" />
                    <Separator/>
                    <MenuItem Click="SelectAll_Click" Header="_Select All" />
                    <MenuItem Click="Clear_Click" Header="_Clear" />
                    <Separator/>
                    <MenuItem Header="Format">
                        <MenuItem Click="TextStyle_Click" Header="_Normal"
                              Name="miNormal"></MenuItem>
                        <MenuItem Click="TextStyle_Click" FontWeight="Bold"
                              Header="_Bold" Name="miBold"></MenuItem>
                        <MenuItem Click="TextStyle_Click" FontStyle="Italic"
                              Header="_Italic" Name="miItalic"></MenuItem>
                    </MenuItem>
                </ContextMenu>
            </TextBox.ContextMenu>
            A TextBox control with ContextMenu.</TextBox>
    </Grid>
</Window>

The following code-behind contains the shared Click event handler to handle format changes and individual Click event handlers for the Clear and Select All context menu items:

using System.Windows;

namespace Recipe_03_15
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles Clear Button Click.
        private void Clear_Click(object sender, RoutedEventArgs e)
        {
            txtTextBox.Clear();
        }

        // Handles the Select All Button Click.
        private void SelectAll_Click(object sender, RoutedEventArgs e)
        {
            txtTextBox.SelectAll();
        }

        // Handles all the Button Click events that format the TextBox.
        private void TextStyle_Click(object sender, RoutedEventArgs e)
        {
            if (sender == miNormal)
            {
                txtTextBox.FontWeight = FontWeights.Normal;
                txtTextBox.FontStyle = FontStyles.Normal;
            }
            else if (sender == miBold)
            {
                txtTextBox.FontWeight = FontWeights.Bold;
            }
            else if (sender == miItalic)
            {
                txtTextBox.FontStyle = FontStyles.Italic;
            }
        }
    }
}

Figure 3-9 shows the two levels of ContextMenu open over the TextBox as defined in this recipe's sample code.

A ContextMenu on a TextBox

Figure 3-9. A ContextMenu on a TextBox

Display a Tool Tip on a Control

Problem

You need to display a tool tip when a user hovers over a UI control with the mouse pointer.

Solution

Assign a System.Windows.Controls.ToolTip control to the ToolTip property of the control on which you want to display the tool tip.

How It Works

The System.Windows.FrameworkElement class implements the ToolTip property, providing a simple mechanism through which to display a tool tip on any FrameworkElement-derived control.

The ToolTip property is of type System.Object, so you can assign it any object, and the property will attempt to render the object as a tool tip for display. This provides a great deal of flexibility in how you define tool tip content. For simple textual tool tips, you can specify the text to display as the value of the control's ToolTip attribute. When creating richer, more complex tool tips, you should use property element syntax to specify structured ToolTip content.

Warning

The content of a ToolTip can contain interactive controls such as buttons, but they never get focus; you can't click or otherwise interact with them.

The Code

The following XAML demonstrates both how to use the ToolTip attribute to specify the tool tip of a Button and how to use a ToolTip object defined using property element syntax to create a tool tip that contains larger and more structured content comprising a label and a list of values:

<Window x:Class="Recipe_03_16.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_16" Height="150" Width="300">
    <StackPanel Name="stackPanel1">
        <Button Height="23" Margin="10" Name="button1"
                ToolTip="A simple textual ToolTip" Width="175">
            Button with Simple ToolTip
        </Button>
        <Button Content="Button with a Richer ToolTip" Height="23"
                Margin="10" Name="button2" Width="175">
            <Button.ToolTip>
                <StackPanel Name="stackPanel2" Width="200">
                    <Label Name="label1" HorizontalAlignment="Left">
                        List of Things:
                    </Label>
                    <ListBox Name="listBox1" Margin="10" >
                        <ListBoxItem>Thing 1</ListBoxItem>
                        <ListBoxItem>Thing 2</ListBoxItem>
                        <ListBoxItem>Thing 3</ListBoxItem>
                    </ListBox>
                 </StackPanel>
             </Button.ToolTip>
         </Button>
    </StackPanel>
</Window>

Figure 3-10 shows the previous example with the ToolTip visible for the lower Button.

Displaying a ToolTip on a Button

Figure 3-10. Displaying a ToolTip on a Button

Display a Tool Tip on a Disabled Control

Problem

You need to display a tool tip on a control even when the control is disabled.

Solution

On the control you want to associate the tool tip with, set the ShowOnDisabled attached property of the System.Windows.Controls.ToolTipService class to True.

How It Works

Usually, disabled controls (those with IsEnabled set to False) do not display tool tips when the user hovers over them with the mouse pointer. The ToolTipService class provides a set of global services that you can use to control the behavior of ToolTip objects. The ToolTipService property that enables the display of tool tips on disabled controls is named ShowOnDisabled. By assigning True to this attached property on a control, you override the default ToolTip behavior and force WPF to display the ToolTip even though the control is disabled.

The Code

The following XAML demonstrates how to use the ToolTipService.ShowOnDisabled attached property to enable the display of a ToolTip for a disabled Button control. The code contains two Button controls: the first shows the default behavior of a ToolTip on a disabled control, and the second shows the effect of setting the ToolTipService.ShowOnDisabled property to True.

<Window x:Class="Recipe_03_17.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_17" Height="150" Width="300">
    <StackPanel>
        <Button Content="Disabled Button without ToolTipService"
            Height="23" IsEnabled="False" Margin="10" Name="button1"
            Width="200">
            <Button.ToolTip>
                ToolTip on a disabled control
            </Button.ToolTip>
        </Button>
        <Button Content="Disabled Button with ToolTipService"
            Height="23" IsEnabled="False" Margin="10" Name="button2"
                ToolTipService.ShowOnDisabled="True" Width="200">
        <Button.ToolTip>
            ToolTip on a disabled control
        </Button.ToolTip>
     </Button>
   </StackPanel>
</Window>

Control the Display Duration and Position of a Tool Tip

Problem

You need to control how long your application displays a System.Windows.Controls.ToolTip or where the ToolTip is located relative to the associated control.

Solution

Apply the attached properties of the System.Windows.Controls.ToolTipService class to the control with which the ToolTip is associated. The ToolTipService.ShowDuration property controls the display duration of the ToolTip. The ToolTipService.Placement property along with the ToolTipService.HorizontalOffset and ToolTipService.VerticalOffset properties control the position of the ToolTip.

How It Works

The ToolTipService class provides a set of attached properties that you can use to control the behavior of ToolTip objects. The ToolTipService.ShowDuration property takes a System.Int32 value that specifies the number of milliseconds for which to display the ToolTip. The default value is 5000.

The ToolTipService.Placement property takes a value from the System.Windows.Controls.Primitives.PlacementMode enumeration, which determines where WPF places the ToolTip when it is displayed. The PlacementMode enumeration contains 12 values, offering a wide choice of how you place your ToolTip. Many of the PlacementMode options are relative to the placement target of the ToolTip. By default, the placement target is the control on which the ToolTip is defined but can be overridden using the ToolTipService.PlacementTarget property. Table 3-5 summarizes some of the more commonly used PlacementMode values.

Table 3-5. Property Values for the Placement of ToolTip Controls

Value

Description

Bottom

The top of the ToolTip is aligned with the bottom of the placement target, and the left edge of the ToolTip is aligned with the left edge of the placement target.

Center

The ToolTip is centered over the placement target.

Left

The right of the ToolTip is aligned with the left of the placement target, and the upper edge of the ToolTip is aligned with the upper edge of the placement target.

Mouse

The top of the ToolTip is aligned with the bottom of the mouse's bounding box, and the left edge of the ToolTip is aligned with the left edge of the mouse's bounding box.

Relative

The upper-left corner of the ToolTip is placed relative to the upper-left corner of the placement target.

Right

The left of the ToolTip is aligned with the right of the placement target, and the upper edge of the ToolTip is aligned with the upper edge of the placement target.

The ToolTipService.HorizontalOffset and ToolTipService.VerticalOffset properties allow you to specify System.Double values to fine-tune the position of the ToolTip depending on the value of the ToolTipService.Placement property. By default, the offset values are assumed to be in px (pixels) but can also be in (inches), cm (centimeters), or pt (points).

Tip

WPF provides fine-grained control over the placement of ToolTip controls using the PlacementTarget, PlacementRectangle, HorizontalOffset, and VerticalOffset properties provided by the ToolTipService. For a thorough description of ToolTip placement logic, see the MSDN article at http://msdn2.microsoft.com/en-us/library/bb613596.aspx.

The Code

The following XAML declares three buttons, each with ToolTip controls using different properties of the ToolTipService class to control position and display duration:

<Window x:Class="Recipe_03_18.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_18" Height="200" Width="300">
    <StackPanel>
        <Button Height="40" Margin="5" ToolTipService.Placement="Mouse"
                ToolTipService.ShowDuration="1000">
            <Button.ToolTip>
                <ToolTip>
                    ToolTip displayed for 1 second...
                </ToolTip>
            </Button.ToolTip>
            Button with ToolTip under Mouse
        </Button>
        <Button Height="40" Margin="5" ToolTipService.Placement="Center">
            <Button.ToolTip>
                <ToolTip>
                    ToolTip displayed for 5 seconds...
                </ToolTip>
            </Button.ToolTip>
            Button with Centered ToolTip
        </Button>
        <Button Height="40" Margin="5" ToolTipService.HorizontalOffset="5cm"
                ToolTipService.Placement="Relative"
                ToolTipService.ShowDuration="10000"
                ToolTipService.VerticalOffset="50px">
            <Button.ToolTip>
                <ToolTip >
                    ToolTip displayed for 10 seconds...
                </ToolTip>
</Button.ToolTip>
       Button with offset ToolTip
     </Button>
   </StackPanel>
</Window>

View and Select Items from a Set of Radio Buttons

Problem

You need to display a set of radio buttons and allow the user to select an item.

Solution

Create a set of System.Windows.Controls.RadioButton controls. To know which particular RadioButton is selected, either test its IsSelected property when the user exits the form containing the RadioButton or handle the RadioButton.Checked event.

How It Works

All RadioButton controls in a single parent container form a single group by default. To create multiple independent groups in a single parent container or to create groups that span multiple containers, set the GroupName property on each RadioButton to define its group membership.

Typically, you consider the state of a set of radio controls when the user submits a form via the click of a button. Unfortunately, there is no straightforward way to identify which RadioButton in a group is checked. Instead, as part of the form-processing logic, you must individually check the IsSelected property of each RadioButton. When you have large numbers or a dynamic set of RadioButton controls, it is easiest to loop through all the controls in a container and filter out those that are not RadioButton controls and those that do not belong to the correct group.

Alternatively, if you need to take an action as the user clicks the RadioButton, assign an event handler to its Checked event.

The Code

The following example demonstrated how to create two groups of RadioButton controls. Using the GroupName property, the example creates two groups within a single System.Windows.Controls.StackPanel (on the left) and also creates a group (Group1) that spans the left and right StackPanel containers.

<Window x:Class="Recipe_03_19.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_19" SizeToContent="Height" Width="300">
<Grid Name="grid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Border Grid.Column="0" BorderBrush="Gray" BorderThickness="1" />
        <Border Grid.Column="1" BorderBrush="Gray" BorderThickness="1" />
        <StackPanel Grid.Column="0" HorizontalAlignment="Center" Margin="5"
                    Name="spLeftContainer">
            <TextBlock FontSize="16" Text="Radio Group 1" />
            <RadioButton Content="Radio Button 1A" GroupName="Group1"
                        IsChecked="True" Margin="5" Name="rbnOneA" />
            <RadioButton Content="Radio Button 1B" GroupName="Group1"
                        Margin="5" Name="rbnOneB" />
            <RadioButton Content="Radio Button 1C" GroupName="Group1"
                        Margin="5" Name="rbnOneC" />
            <Separator/>
            <TextBlock FontSize="16" Text="Radio Group 2" />
            <RadioButton Checked="RadioButton_Checked" GroupName="Group2"
                         Content="Radio Button 2A" IsChecked="True"
                         Margin="5" Name="rbnTwoA" />
            <RadioButton Checked="RadioButton_Checked" GroupName="Group2"
                         Content="Radio Button 2B" Margin="5" Name="rbnTwoB"/>
            <RadioButton Checked="RadioButton_Checked" GroupName="Group2"
                         Content="Radio Button 2C" Margin="5" Name="rbnTwoC"/>
     </StackPanel>
     <StackPanel Grid.Column="1" HorizontalAlignment="Center" Margin="5"
                 Name="spRightContainer">
            <TextBlock FontSize="16" Text="Radio Group 1" />
            <RadioButton Content="Radio Button 1D" GroupName="Group1"
                         Margin="5" Name="rbnOneD" />
            <RadioButton Content="Radio Button 1E" GroupName="Group1"
                         Margin="5" Name="rbnOneE" />
        </StackPanel>
        <Button Content="Show Group1 Selection" Grid.ColumnSpan="2"
                Grid.Row="1" HorizontalAlignment="Center"
                Margin="10" MaxHeight="25" Click="Button_Click" />
    </Grid>
</Window>

The following code-behind demonstrates how to handle the RadioButton.Checked event and also how to loop through the children of a container to determine which RadioButton from a particular group is checked:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_19
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
         public Window1()
         {
             InitializeComponent();
         }

         // Handles the Submit Button Click event.
         private void Button_Click(object sender, RoutedEventArgs e)
         {
             RadioButton radioButton = null;

             // Try the first (left) container to see if one of
             // the radio buttons in Group1 is checked.
             radioButton = GetCheckedRadioButton(
                 spLeftContainer.Children, "Group1");

             // If no RadioButton in the first container is checked, try
             // the second (right) container.
             if (radioButton == null)
             {
                 radioButton = GetCheckedRadioButton(
                     spRightContainer.Children, "Group1");
             }

             // We must have at least one RadioButton checked to display.
             MessageBox.Show(radioButton.Content + " checked.", Title);
         }

         // A method that loops through a UIElementCollection and identifies
         // a checked RadioButton with a specified group name.
         private RadioButton GetCheckedRadioButton(
             UIElementCollection children, String groupName)
         {
return children.OfType<RadioButton>().
         FirstOrDefault( rb => rb.IsChecked == true
             && rb.GroupName == groupName);
     }

     // Handles the RadioButton Checked event for all buttons in Group2.
     private void RadioButton_Checked(object sender, RoutedEventArgs e)
     {
         // Don't handle events until the Window is fully initialized.
         if (!this.IsInitialized) return;

         RadioButton radioButton = e.OriginalSource as RadioButton;

         if (radioButton != null)
         {
             MessageBox.Show(radioButton.Content + " checked.", Title);
         }
      }
   }
}

Figure 3-11 shows the two groups of RadioButton controls created in the previous example.

Creating sets of RadioButton controls

Figure 3-11. Creating sets of RadioButton controls

View and Select Items from a Set of Check Boxes

Problem

You need to display a set of check boxes and allow the user to select items.

Solution

Create a set of System.Windows.Controls.CheckBox controls. To know which particular CheckBox is selected, test the IsChecked property of each CheckBox when the user exits the form containing the set of CheckBox controls. To respond as soon as the user clicks a CheckBox to change its state, handle the Checked, Unchecked, and Indeterminate events of the CheckBox control.

How It Works

To determine the state of a CheckBox, test the value of its IsChecked property. The IsChecked property is of type bool?, meaning it can be True, False, or Null. A value of True means the CheckBox is checked, False means it is unchecked, and Null means it is in an indeterminate state. By default, the user cannot switch a CheckBox into an indeterminate state, but by setting CheckBox.IsThreeState to True, the user can click the CheckBox to toggle through the three states.

To respond as soon as the user clicks a CheckBox, you could handle the CheckBox.Click event and determine what state the CheckBox is in, but it is easier to handle the Checked, Unchecked, and Indeterminate events that the CheckBox raises as it is entering the appropriate state (that is, as the CheckBox gets unchecked, it raises the Unchecked event).

The Code

The following example demonstrates two approaches to determining the state of a set of CheckBox controls. The XAML defines a simple window containing four CheckBox controls (two enabled with tristate support). Each time the user changes the state of a CheckBox, the example shows a message box describing the change. The user can also click a button, which will populate a list with the names of the CheckBox controls that are currently checked.

<Window x:Class="Recipe_03_20.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_20" Height="250" Width="300">
    <StackPanel Name="panel">
        <CheckBox Checked="CheckBox_Checked" Content="First CheckBox"
                  IsChecked="True" Margin="2" Name="checkbox1"
                  Unchecked="CheckBox_Unchecked"/>
        <CheckBox Checked="CheckBox_Checked" Content="Second CheckBox"
                  IsChecked="False" Margin="2" Name="checkbox2"
                  Unchecked="CheckBox_Unchecked"/>
        <CheckBox Checked="CheckBox_Checked"
                  Content="Third CheckBox (Tri-State Enabled)"
                  Indeterminate="CheckBox_Indeterminate" IsChecked="True"
                  IsThreeState="True" Margin="2" Name="checkbox3"
                  Unchecked="CheckBox_Unchecked"/>
        <CheckBox Checked="CheckBox_Checked"
                  Content="Fourth CheckBox (Tri-State Enabled)"
                  Indeterminate="CheckBox_Indeterminate" IsChecked="False"
                  IsThreeState="True" Margin="2" Name="checkbox4"
                  Unchecked="CheckBox_Unchecked"/>
<Button Content="Get Selected" Margin="5" MaxWidth="100"
                Click="Button_Click" />
        <TextBlock FontWeight="Bold" Text="Selected CheckBoxes:" />
        <ListBox Margin="5" MinHeight="2cm" Name="listbox" />
    </StackPanel>
</Window>

The following code-behind provides the logic for processing the Button.Cick event to populate the list with the names of the checked CheckBox controls and the event handlers for displaying message boxes as the state of a CheckBox changes:

using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_20
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles Button Click event to populate the ListBox with
        // the names of the currently checked CheckBox controls.
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Clear the content of the ListBox.
            listbox.Items.Clear();

            // Process each CheckBox control in the main StackPanel.
            foreach (CheckBox checkbox in panel.Children.OfType<CheckBox>()
                .Where( cb => cb.IsChecked == true))
            {
                listbox.Items.Add(checkbox.Name);
            }
        }

        // Handles all the CheckBox Checked events to display a message
        // when a CheckBox changes to a checked state.
        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        {
            // Don't handle these events during initialization.
            if (!IsInitialized) return;
CheckBox checkbox = e.OriginalSource as CheckBox;

       if (checkbox != null)
       {
           MessageBox.Show(checkbox.Name + " is checked.", Title);
       }
     }

     // Handles all the CheckBox Indeterminate events to display a message
     // when a CheckBox changes to an indeterminate state.
     private void CheckBox_Indeterminate(object sender, RoutedEventArgs e)
     {
         // Don't handle these events during initialization.
         if (!IsInitialized) return;

         CheckBox checkbox = e.OriginalSource as CheckBox;

         if (checkbox != null)
         {
             MessageBox.Show(checkbox.Name + " is indeterminate.", Title);
         }
     }

     // Handles all the CheckBox Unchecked events to display a message
     // when a CheckBox changes to an unchecked state.
     private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
     {
         // Don't handle these events during initialization.
         if (!IsInitialized) return;

         CheckBox checkbox = e.OriginalSource as CheckBox;

         if (checkbox != null)
         {
             MessageBox.Show(checkbox.Name + " is unchecked.", Title);
         }
      }
   }
}

Figure 3-12 shows the previous example after the user has toggled the fourth CheckBox into an indeterminate state and has clicked the Get Selected Button.

Creating sets of CheckBox controls

Figure 3-12. Creating sets of CheckBox controls

View and Select Items Using a Tree

Problem

You need to present a hierarchical set of data as a tree with collapsible branches and allow a user to select an item from the tree.

Solution

Use the System.Windows.Controls.TreeView control to present an expandable tree of items. Use the SelectedItem property of the TreeView control to determine the currently selected item. To respond to the selection of items in the ListBox dynamically, handle the TreeView.SelectionChanged event or the Selected event raised by the System.Windows.Controls.TreeViewItem objects that wrap each element contained in the TreeView controls.

How It Works

The easiest way to define the content of a static TreeView is to include the content elements directly within the XAML TreeView element. You can include controls directly within the TreeView element XAML or, for greater control over the formatting of the contained items, wrap them in a TreeViewItem element.

Note

A more flexible way to define the content of a TreeView is to data bind it to a collection. Chapter 5 describes how to use data binding.

To determine the current TreeView selection, you can get the SelectedItem property of the TreeView at an appropriate time (that is, when the user submits or closes the form containing the TreeView), or you can handle selection dynamically by handling events raised by the TreeView and the individual TreeViewItem objects.

On a change in selection, the TreeView raises a SelectedItemChanged. The individual TreeViewItem objects raise a Selected event that contains details that allow you to identify that the TreeViewItem has changed. When a TreeViewItem raises the Selected event, the hierarchy of parent TreeViewItem objects that contain the item each raise a Selected event in turn.

The Code

The following example demonstrates how to use a TreeView control to display hierarchical information. As the user selects an item, the example shows a MessageBox for each TreeViewItem.Selected event raised. The user can also click the Button to see a message box identifying the header of the currently selected TreeViewItem. The example uses a Style with an EventSetter element to assign a common Selected event handler to all instances of TreeViewItem.

<Window x:Class="Recipe_03_21.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="WPF Recipes 3_21" Height="200" Width="300">
   <DockPanel LastChildFill="True">
     <DockPanel.Resources>
       <Style TargetType="{x:Type TreeViewItem}">
         <EventSetter Event="Selected"
                         Handler="TreeViewItem_Selected" />
       </Style>
     </DockPanel.Resources>
     <Button Click="Button_Click" DockPanel.Dock="Bottom"
                Content="Show Selected" MaxHeight="23" MaxWidth="100" />
     <TreeView FontSize="16" Name="tvTree">
       <TreeViewItem Header="Birds" IsExpanded="True">
         <TreeViewItem Header="Flighted">
           <TreeViewItem Header="Falcon" />
           <TreeViewItem Header="Starling" />
         </TreeViewItem>
         <TreeViewItem Header="Flightless" IsExpanded="True">
           <TreeViewItem Header="Emu" />
           <TreeViewItem Header="Kiwi" />
         </TreeViewItem>
       </TreeViewItem>
       <TreeViewItem Header="Reptiles">
         <TreeViewItem Header="Lizards">
           <TreeViewItem Header="Blue Tonge" />
           <TreeViewItem Header="Frilled" />
           <TreeViewItem Header="Iguana" />
         </TreeViewItem>
<TreeViewItem Header="Snakes">
           <TreeViewItem Header="Anaconda" />
           <TreeViewItem Header="Cobra" />
           <TreeViewItem Header="Rattlesnake" />
         </TreeViewItem>
       </TreeViewItem>
     </TreeView>
   </DockPanel>
</Window>

The following code contains the event-handling logic for the TreeView example:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_21
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles the Selected event for all TreeViewItems.
        private void TreeViewItem_Selected(object sender,
            RoutedEventArgs e)
        {
            String message = String.Empty;

            // As the Selected event is fired by successive
            // parent TreeViewItem controls of the actually
            // selected TreeViewItem, the sender will change,
            // but the e.OriginalSource will continue to
            // refer to the TreeViewItem that was actually
            // clicked.
            TreeViewItem item = sender as TreeViewItem;

            if (item == e.OriginalSource)
            {
                // Event raised by clicked item.
                message =
                    String.Format("Item selected: {0} ({1} child items)",
                    item.Header, item.Items.Count);
            }
else
         {
           // Event raised by a parent of clicked item.
           message =
               String.Format("Parent of selected: {0} ({1} child items)",
               item.Header, item.Items.Count);
       }

       MessageBox.Show(message, Title);
     }

     // Handles the Button Click event.
     private void Button_Click(object sender, RoutedEventArgs e)
     {
         TreeViewItem item = tvTree.SelectedItem as TreeViewItem;

         if (item != null)
         {
             MessageBox.Show("Item selected: " + item.Header, Title);
         }
         else
         {
             MessageBox.Show("No item selected", Title);
         }
      }
   }
}

Figure 3-13 shows the example in its initial state with the Birds and Flightless items expanded by default.

Hierarchical data shown in a TreeView control

Figure 3-13. Hierarchical data shown in a TreeView control

View and Select Items Using a List

Problem

You need to present a scrollable list of items and allow the user to select an item from the list.

Solution

Use the System.Windows.Controls.ListBox control to present a list of items. Use the SelectedItem property of the ListBox control to determine the currently selected item (use SelectedItems for multiselect lists). To respond to the selection of items in the ListBox dynamically, handle the ListBox.SelectionChanged event or the Selected event raised by the System.Windows.Controls.ListBoxItem objects that wrap each element contained in the ListBox controls.

How It Works

The ListBox control makes it incredibly easy to present a set of things to the user as a list. In fact, you can put anything that derives from System.Object into a ListBox. Any list item derived from System.Windows.UIElement will render according to its OnRender implementation, whereas other items will be rendered using ToString.

The easiest way to define the content of a static list is to include the content elements directly within the XAML ListBox element. You can include controls directly within the ListBox element XAML or, for greater control over the formatting of the contained items, wrap them in a ListBoxItem element.

To determine the current ListBox selection, you can process the SelectedItem or SelectedItems properties of the ListBox at an appropriate time (that is, when the user submits or closes the form containing the ListBox), or you can handle selection dynamically by handling events raised by the ListBox and the individual ListBoxItem objects. On a change in selection, the ListBox raises a SelectionChanged event that passes details of all the items currently selected along with those that were just unselected. The individual ListBoxItem objects also raise a Selected event that contains details that allow you to identify the ListBoxItem that has changed.

Note

Recipe 3-23 describes how to add and remove items from a ListBox programmatically. Also, the flexibility of the ListBox control means that it is a useful base from which to create rich custom controls that look and behave differently than the generic ListBox (see Chapter 4 and Chapter 6 for more details).

The Code

The following XAML defines in single-select ListBox containing a variety of other controls, including a nested ListBox. Some items are contained directly within the ListBox, while others are wrapped in ListBoxItem elements.

<Window x:Class="Recipe_03_22.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="WPF Recipes 3_22" Height="300" Width="300">
   <StackPanel>
     <ListBox SelectionChanged="OuterListBox_SelectionChanged"
                 Name="outerListBox">
            <ListBoxItem Content="List Box Item 1" FontFamily="Tahoma"
                            FontSize="14" HorizontalContentAlignment="Left" />
            <ListBoxItem Content="List Box Item 2" FontFamily="Algerian"
                            FontSize="16" HorizontalContentAlignment="Center" />
            <ListBoxItem Content="List Box Item 3" FontSize="20"
                            FontFamily="FreeStyle Script"
                            HorizontalContentAlignment="Right" />
            <Button Content="Button directly in a list" Margin="5" />
            <ListBoxItem HorizontalContentAlignment="Center" Margin="5">
                   <Button Content="Button wrapped in ListBoxItem" />
            </ListBoxItem>
            <ListBox Height="50" Margin="5">
                   <ListBoxItem Content="Inner List Item 1"
                                   Selected="InnerListBoxItem_Selected" />
                   <ListBoxItem Content="Inner List Item 2"
                                   Selected="InnerListBoxItem_Selected" />
                   <ListBoxItem Content="Inner List Item 3"
                                   Selected="InnerListBoxItem_Selected" />
                   <ListBoxItem Content="Inner List Item 4"
                                   Selected="InnerListBoxItem_Selected" />
            </ListBox>
            <StackPanel Margin="5" Orientation="Horizontal">
                   <Label Content="Enter some text:" />
                   <TextBox MinWidth="150" />
            </StackPanel>
       </ListBox>
       <TextBlock Text="No item currently selected." Margin="10"
                     HorizontalAlignment="Center" Name="txtSelectedItem" />
     </StackPanel>
</Window>

The following code-behind contains the logic used to handle the event raised by the outer ListBox and the items contained in the inner ListBox. When the user selects an item in the outer ListBox, the code handles the ListBox.SelectionChanged event and displays the ToString output of the selected item at the bottom of the form. When the user selects an item in the inner ListBox, the code handles the ListBoxItem.Selected event and displays the Content property of the selected item in a MessageBox.

using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_22
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Handles ListBoxItem Selected events for the ListBoxItems in the
        // inner ListBox.
        private void InnerListBoxItem_Selected(object sender,
            RoutedEventArgs e)
        {
            ListBoxItem item = e.OriginalSource as ListBoxItem;

            if (item != null)
            {
                MessageBox.Show(item.Content + " was selected.", Title);
            }
        }

        // Handles ListBox SelectionChanged events for the outer ListBox.
        private void OuterListBox_SelectionChanged(object sender,
            SelectionChangedEventArgs e)
        {
            object item = outerListBox.SelectedItem;

            if (item == null)
            {
                txtSelectedItem.Text = "No item currently selected.";
            }
            else
            {
               txtSelectedItem.Text = item.ToString();
            }
        }
    }
}

Figure 3-14 shows the ListBox from the example. The first button is the currently selected item.

A ListBox containing a rich set of controls as list elements

Figure 3-14. A ListBox containing a rich set of controls as list elements

Dynamically Add Items to a List

Problem

You need to add items to, and remove items from, a System.Windows.Controls.ListBox control at runtime.

Solution

To add an item, create a new System.Windows.Controls.ListBoxItem object, configure it, and add it to the ListBox using the ListBox.Items.Add method. To remove an item, use the ListBox.Items.Remove method.

How It Works

The content of a ListBox is contained in a System.Windows.Controls.ItemCollection collection, which is accessed via the ListBox.Items property. By modifying the content of the ItemCollection, you control the visible content of the ListBox.

You can add any object to the ItemCollection. Any list item derived from System.Windows.UIElement will render according to its OnRender implementation, whereas other items will be rendered using ToString. Wrapping the object in a ListBoxItem gives you greater control over the format and layout of the item when it is displayed in the ListBox.

Note

Recipe 3-22 describes the basic structure of a ListBox and ListBoxItem in more detail.

The Code

The following XAML defines a simple ListBox control in Extended selection mode containing some statically defined items. The example contains System.Windows.Controls.TextBox and System.Windows.Controls.Button controls that allow the user to add and remove items from the list.

<Window x:Class="Recipe_03_23.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_23" Height="300" Width="300">
    <StackPanel>
        <ListBox FontSize="16" Height="150" Margin="5" Name="listBox1"
                 SelectionMode="Extended">
            <ListBoxItem>List Item 1</ListBoxItem>
            <ListBoxItem>List Item 2</ListBoxItem>
            <ListBoxItem>List Item 3</ListBoxItem>
        </ListBox>
        <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Label Content="_New item text:" VerticalAlignment="Center"
                   Target="{Binding ElementName=textBox}" />
            <TextBox Margin="5" Name="textBox" MinWidth="120" />
        </StackPanel>
        <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <utton Click="btnAddListItem_Click" Content="Add Item"
                    IsDefault="True" Margin="5" Name="btnAddListItem" />
            <Button Click="btnDeleteListItem_Click" Content="Delete Items"
                    Margin="5" Name="btnDeleteListItem" />
            <Button Click="btnSelectAll_Click" Content="Select All"
                    Margin="5" Name="btnSelectAll" />
        </StackPanel>
    </StackPanel>
</Window>

The following code-behind handles the Button click events that add and remove items from the list. When the user clicks the Add Item Button, the event handler gets the content of the TextBox, wraps it in a ListBoxItem, formats it, and adds it to the ListBox. When the user clicks the Delete Items Button, the event handler loops through the set of currently selected items and removes them from the ListBox.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Recipe_03_23
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
public partial class Window1 : Window
   {
     public Window1()
     {
         InitializeComponent();
     }

     // Handles the Add Item Button Click event.
     private void btnAddListItem_Click(object sender,
         RoutedEventArgs e)
     {
         // Ensure there is text to add.
         if (textBox.Text.Length == 0)
         {
             MessageBox.Show("Enter text to add to the list.", Title);
         }
         else
         {
             // Wrap the text in a ListBoxItem and configure.
             ListBoxItem item = new ListBoxItem();
             item.Content = textBox.Text;
             item.IsSelected = true;
             item.HorizontalAlignment = HorizontalAlignment.Center;
             item.FontWeight = FontWeights.Bold;
             item.FontFamily = new FontFamily("Tahoma");

             // Add the ListBoxItem to the ListBox
             listBox1.Items.Add(item);

             // Clear the content of the textBox and give it focus.
             textBox.Clear();
             textBox.Focus();
          }
       }

       // Handles the Delete Item Button Click event.
       private void btnDeleteListItem_Click(object sender,
           RoutedEventArgs e)
       {
           // Ensure there is at least one item selected.
           if (listBox1.SelectedItems.Count == 0)
           {
               MessageBox.Show("Select list items to delete.", Title);
           }
else
       {
           // Iterate through the selected items and remove each one.
           // Cannot use foreach because we are changing the underlying
           // data.
           while (listBox1.SelectedItems.Count > 0)
           {
               listBox1.Items.Remove(listBox1.SelectedItems[0]);
           }
        }
     }

     // Handles the Select All Button Click event.
     private void btnSelectAll_Click(object sender, RoutedEventArgs e)
     {
         listBox1.SelectAll();
     }
   }
}

View and Select Items Using a Combo Box

Problem

You need to present a list of items as an expandable combo box and allow the user to select an item from the list.

Solution

Use the System.Windows.Controls.ComboBox control to present the expandable list of items. Use the SelectedItem property of the ComboBox control to identify the currently selected item. To respond to the selection of items in the ComboBox dynamically, handle the ComboBox.SelectionChanged event or the Selected event raised by the System.Windows.Controls.ComboBoxItem objects that wrap each element contained in the ComboBox control.

How It Works

The ComboBox control inherits from the System.Windows.Controls.ItemsControl the same as the System.Windows.Controls.ListBox, and they are similar in their use (recipes 3-22 and 3-23 demonstrate how to use the ListBox control). The key difference between the ListBox and the ComboBox is the way they are rendered, which results in the ComboBox allowing the user to select only one item at a time. The other key difference is that by setting the ComboBox.IsEditable property to True, you allow the user to type a value into the ComboBox instead of being able to choose only one of the values in the drop-down list.

As with a ListBox, you can put anything that derives from System.Object into a ComboBox. Any list item derived from System.Windows.UIElement will render according to its OnRender implementation, whereas other items will be rendered using ToString.

To define static content for a ComboBox, include the content elements directly within the XAML ComboBox element. You can include controls directly within the ComboBox element XAML or, for greater control over the formatting of the contained items, wrap them in a ComboBoxItem element.

To determine the current ComboBox selection, you can query the SelectedItem property of the ComboBox at an appropriate time (that is, when the user submits or closes the form containing the ComboBox). If the SelectedItem property is Null and IsEditable is True, you can determine whether the user has typed a value into the ComboBox via the ComboBox.Text property.

To handle ComboBox selection more dynamically, you can handle the events raised by the ComboBox and the individual ComboBoxItem objects. On a change in selection, the ComboBox raises a SelectionChanged event that passes details of the item currently selected. Alternatively, when newly selected, a ComboBoxItem raises a Selected event, which contains details that allow you to identify the individual ComboBoxItem that raised the event.

The Code

The following XAML defines a ComboBox containing five ComboBoxItem items and sets the third item to be selected using the IsSelected property. The Get Selected button causes the example to determine which ComboBoxItem is currently selected. As the user changes their selection, the example handles both ComboBoxItem.Selected and ComboBox.SelectionChanged events, displaying messages about which item is now selected.

<Window x:Class="Recipe_03_24.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_24" Height="100" Width="300">
    <StackPanel>
        <ComboBox Name="comboBox" IsEditable="True" Margin="5"
                  SelectionChanged="ComboBox_SelectionChanged">
            <ComboBoxItem Content="ComboBox Item 1"
                          Selected="ComboBoxItem_Selected" />
            <ComboBoxItem Content="ComboBox Item 2"
                          Selected="ComboBoxItem_Selected" />
            <ComboBoxItem Content="ComboBox Item 3"
                          Selected="ComboBoxItem_Selected" IsSelected="True"/>
            <ComboBoxItem Content="ComboBox Item 4"
                          Selected="ComboBoxItem_Selected" />
            <ComboBoxItem Content="ComboBox Item 5"
                          Selected="ComboBoxItem_Selected" />
        </ComboBox>
        <Button Content="Get Selected" Margin="5" Width="100"
                Click="Button_Click" />
    </StackPanel>
</Window>

The following code-behind contains the event handlers used by the preceding XAML:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Recipe_03_24
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Gets the currently selected ComboBoxItem when the user
        // clicks the Button. If the SelectedItem of the ComboBox
        // is null, the code checks to see if the user has entered
        // text into the ComboBox instead.
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ComboBoxItem item = comboBox.SelectedItem as ComboBoxItem;

            if (item != null)
            {
                MessageBox.Show("Current item: " + item.Content, Title);
            }
            else if (!String.IsNullOrEmpty(comboBox.Text))
            {
                MessageBox.Show("Text entered: " + comboBox.Text, Title);
            }
         }

         // Handles ComboBox SelectionChanged events.
         private void ComboBox_SelectionChanged(object sender,
             SelectionChangedEventArgs e)
     {
             // Do not handle events until Window is fully initialized.
             if (!IsInitialized) return;

             ComboBoxItem item = comboBox.SelectedItem as ComboBoxItem;
if (item != null)
       {
          MessageBox.Show("Selected item: " + item.Content, Title);
       }
     }

     // Handles ComboBoxItem Selected events.
     private void ComboBoxItem_Selected(object sender,
         RoutedEventArgs e)
     {
         // Do not handle events until Window is fully initialized.
         if (!IsInitialized) return;

         ComboBoxItem item = e.OriginalSource as ComboBoxItem;

         if (item != null)
         {
             MessageBox.Show(item.Content + " was selected.", Title);
         }
      }
   }
}

Display a Control Rotated

Problem

You need to display a control rotated from its normal horizontal or vertical axis.

Solution

Apply a LayoutTransform or a RenderTransform to the control.

How It Works

WPF makes many things trivial that are incredibly complex to do in Windows Forms programming. One of those things is the ability to rotate controls to any orientation yet still have them appear and function as normal. Admittedly, it is not every day you need to display a rotated control, but when you do, you will appreciate how easy it is in WPF. Most frequently, the ability to rotate controls becomes important when you start to customize the appearance of standard controls using templates (as discussed in Chapter 6) or when you create custom controls (as discussed in Chapter 4).

Both the LayoutTransform and RenderTransform have a RotateTransform property, in which you specify the angle in degrees you want your control rotated by. Positive values rotate the control clockwise, and negative values rotate the control counterclockwise. The rotation occurs around the point specified by the CenterX and CenterY properties. These properties refer to the coordinate space of the control that is being transformed, with (0,0) being the upper-left corner. Alternatively, you can use the RenderTransformOrigin property on the control you are rotating; this allows you to specify a point a relative distance from the origin using values between 0 and 1 that WPF automatically converts to specific values.

The difference between the LayoutTransform and RenderTransform is the order in which WPF executes the transformation. WPF executes the LayoutTransform as part of the layout processing, so the rotated position of the control affects the layout of controls around it. The RenderTransform, on the other hand, is executed after layout is determined, which means the rotated control does not affect the positioning of other controls and can therefore end up appearing partially over or under other controls.

The Code

The following XAML demonstrates a variety of rotated controls. The bottom left shows the difference in behavior of a LayoutTransform compared to a RenderTransform (shown in the bottom-right corner). See Figure 3-15.

<Window x:Class="Recipe_03_25.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 3_25" Height="350" Width="400">
    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition MinHeight="140" />
            <RowDefinition MinHeight="170" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBox Grid.Row="0" Grid.Column="0" Height="23"
                 HorizontalAlignment="Center" Text="An upside down TextBox."
                 Width="140">
            <TextBox.LayoutTransform>
                <RotateTransform Angle="180"/>
            </TextBox.LayoutTransform>
        </TextBox>
        <Button Content="A rotated Button" Grid.Row="0" Grid.Column="1"
                Height="23" Width="100">
            <Button.LayoutTransform>
                <RotateTransform Angle="-120"/>
            </Button.LayoutTransform>
        </Button>
        <StackPanel Grid.Row="1" Grid.Column="0" >
            <TextBlock HorizontalAlignment="Center" Margin="5">
                Layout Tranform
            </TextBlock>
            <Button Margin="5" Width="100">Top Button</Button>
<Button Content="Middle Button" Margin="5" Width="100">
                    <Button.LayoutTransform>
                        <RotateTransform Angle="30" />
                    </Button.LayoutTransform>
                </Button>
                <Button Margin="5" Width="100">Bottom Button</Button>
            </StackPanel>
            <StackPanel Grid.Row="1" Grid.Column="1" >
            <TextBlock HorizontalAlignment="Center" Margin="5">
                Render Tranform
            </TextBlock>
            <Button Margin="5" Width="100">Top Button</Button>
            <Button Content="Middle Button" Margin="5"
                    RenderTransformOrigin="0.5, 0.5" Width="100">
                <Button.RenderTransform>
                    <RotateTransform Angle="30" />
                </Button.RenderTransform>
            </Button>
            <Button Margin="5" Width="100">Bottom Button</Button>
        </StackPanel>
    </Grid>
</Window>

Figure 3-15 shows a variety of rotated controls.

A set of rotated controls

Figure 3-15. A set of rotated controls

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

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