Chapter 12. Dealing with Multimedia and User Input

Being a technology for developing rich user interfaces, it makes sense that WPF should provide good support for integrating video and audio into your applications and for getting input from the user. This chapter takes a look at some of the video, audio, and user input capabilities provided by WPF.

As with so many things, WPF makes it relatively easy to add strong multimedia and user input support to your applications. The recipes in this chapter describe how to:

Play System Sounds

Problem

You need to play one of the standard Windows system sounds.

Solution

Use the static properties of the System.Media.SystemSounds class to obtain a System.Media.SystemSound object representing the sound you want to play. Then call the Play method on the SystemSound object.

How It Works

The SystemSounds class provides a simple way of playing some of the most commonly used standard Windows system sounds. The SystemSounds class implements five static properties: Asterisk, Beep, Exclamation, Hand, and Question. Each of these properties returns a SystemSound object representing a particular sound. Once you have the appropriate SystemSound object, simply call its Play method to play the sound.

The sound played by each SystemSound object depends on the user's Windows configuration on the Sounds tab of the Sounds and Audio Devices control panel. If the user has no sound associated with the specific type of event, calling Play on the related SystemSound object will make no sound. You have no control over any aspect of the sound playback such as volume or duration.

The Code

The following XAML displays five buttons (see Figure 12-1). Each button is configured to play a different SystemSound using the SystemSounds class in the code-behind:

<Window x:Class="Recipe_12_01.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_01" Height="120" Width="300">
    <Canvas>
        <Canvas.Resources>
            <!-- Style all buttons the same -->
            <Style TargetType="{x:Type Button}">
                <Setter Property="Height" Value="25" />
                <Setter Property="MinWidth" Value="70" />
                <EventSetter Event="Click" Handler="Button_Click" />
            </Style>
        </Canvas.Resources>
        <Button Canvas.Top="15" Canvas.Left="30"
            Content="Asterisk" Name="btnAsterisk" />
        <Button Canvas.Top="15" Canvas.Left="110"
            Content="Beep" Name="btnBeep" />
        <Button Canvas.Top="15" Canvas.Left="190"
            Content="Exclamation" Name="btnExclamation" />
        <Button Canvas.Top="50" Canvas.Left="70"
            Content="Hand" Name="btnHand" />
        <Button Canvas.Top="50" Canvas.Left="150"
            Content="Question" Name="btnQuestion" />
   </Canvas>
</Window>

The following code-behind determines which button the user has clicked and plays the appropriate sound:

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

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

        // Handles the click events for all system sound buttons.
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;

            if (btn != null)
            {
                // Simple switch on the name of the button.
                switch (btn.Content.ToString())
               {
                    case "Asterisk":
                         System.Media.SystemSounds.Asterisk.Play();
                         break;
                    case "Beep":
                         System.Media.SystemSounds.Beep.Play();
                         break;
                    case "Exclamation":
                         System.Media.SystemSounds.Exclamation.Play();
                         break;
                    case "Hand":
                         System.Media.SystemSounds.Hand.Play();
                         break;
                    case "Question":
                         System.Media.SystemSounds.Question.Play();
                         break;
default:
                         string msg = "Sound not implemented: " + btn.Content;
                         MessageBox.Show(msg);
                         break;
               }
           }
       }
   }
}
A set of buttons playing system sounds

Figure 12-1. A set of buttons playing system sounds

Use Triggers to Play Audio When a User Interacts with a Control

Problem

You need to play a sound when the user interacts with a control, such as clicking a button or moving a slider.

Solution

Declare a System.Windows.Controls.MediaElement on your form. Configure an EventTrigger on the control, and use a StoryBoard containing a System.Windows.Media.MediaTimeline to play the desired audio through the MediaElement in response to the appropriate event.

How It Works

An EventTrigger hooks the event specified in its RoutedEvent property. When the event fires, the EventTrigger applies the animation specified in its Actions property. As the action, you can configure the animation to play a media file using a MediaTimeline.

You can define an EventTrigger directly on a control by declaring it in the Triggers collection of the control. In the RoutedEvent property of the EventTrigger, specify the name of the event you want to trigger the sound, for example, Button.Click. Within the Actions element of the Triggers collection, declare a BeginStoryboard element containing a Storyboard element. In the Storyboard element, you declare the MediaTimeline.

You specify the media file to play using the Source property of the MediaTimeline and the MediaElement that will actually do the playback in the Storyboard.TargetName property. When the user interacts with the control, the specified sound will play back asynchronously.

Note

Chapter 6 provides more details on the use of triggers, and Chapter 11 provides extensive coverage of animation in WPF.

The Code

The following XAML demonstrates how to assign an EventTrigger to the Click event of a System.Windows.Controls.Button and the ValueChanged event of a System.Windows.Controls. Slider. Figure 12-2 shows the example running.

<Window x:Class="Recipe_12_02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_02" Height="100" Width="300">
    <StackPanel>
        <!-- MediaElement for sond playback. -->
        <MediaElement Name="meMediaElem" />
        <!-- The Button that goes Ding! -->
        <UniformGrid Height="70" Columns="2">
            <Button Content="Ding" MaxHeight="25" MaxWidth="70">
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <MediaTimeline
                                        Source="ding.wav"
                                        Storyboard.TargetName="meMediaElem"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
            <!-- The Slider that goes Ring! -->
            <Slider MaxHeight="25" MaxWidth="100" >
                <Slider.Triggers>
                    <EventTrigger RoutedEvent="Slider.ValueChanged">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <MediaTimeline
Source="ringin.wav"
                                        Storyboard.TargetName="meMediaElem" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Slider.Triggers>
            </Slider>
       </UniformGrid>
   </StackPanel>
</Window>
Playing sound using triggers

Figure 12-2. Playing sound using triggers

Play a Media File

Problem

You need to play a sound or music file and allow the user to control the progress of the playback, volume, or balance.

Solution

Use a System.Windows.Controls.MediaElement to handle the playback of the media file. Use a System.Windows.Media.MediaTimeline to control the playback of the desired media through the MediaElement. Declare the set of controls that will enable the user to control the playback and associate triggers with these controls that start, stop, pause, and resume the animation controlling the MediaTimeline. For volume and balance, data bind controls to the Volume1 and Balance properties of the MediaElement.

How It Works

A MediaElement performs the playback of a media file, and you control that playback via animation using a MediaTimeline. To control the playback, you use a set of EventTrigger elements to start, stop, pause, and resume the animation Storyboard containing the MediaTimeline.

Either you can define the EventTrigger elements in the Triggers collection on the controls that control the playback or you can centralize their declaration by placing them on the container in which you place the controls. Within the Actions element of the Triggers collection, declare the Storyboard elements to control the MediaTimeline.

Note

Chapter 5 provides more details on using data binding, Chapter 6 discusses using triggers in more detail, and Chapter 11 provides extensive coverage of animation in WPF.

One complexity arises when you want a control, such as a System.Windows.Controls.Slider, to show the current position within the media file as well as allow the user to change the current play position. To update the display of the current play position, you must attach an event handler to the MediaTimeline.CurrentTimeInvalidated event, which updates the Slider position when it fires.

To move the play position in response to the Slider position changing, you attach an event handler to the Slider.ValueChanged property, which calls the Stoyboard.Seek method to change the current MediaTimeline play position. However, you must include logic in the event handlers to stop these events from triggering each other repeatedly as the user and MediaTimeline try to update the Slider position (and in turn the media play position) at the same time.

The Code

The following XAML demonstrates how to play an AVI file using a MediaElement and allow the user to start, stop, pause, and resume the playback. The user can also move quickly back and forth through the media file using a slider to position the current play position, as well as control the volume and balance of the audio (see Figure 12-3).

<Window x:Class="Recipe_12_03.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_03" Height="450" Width="300">
    <StackPanel x:Name="Panel">
        <StackPanel.Resources>
             <!-- Style all buttons the same. -->
             <Style TargetType="{x:Type Button}">
                  <Setter Property="Height" Value="25" />
                  <Setter Property="MinWidth" Value="50" />
             </Style>
        </StackPanel.Resources>
        <StackPanel.Triggers>
             <!-- Triggers for handling playback of media file. -->
             <EventTrigger RoutedEvent="Button.Click" SourceName="btnPlay">
                 <EventTrigger.Actions>
                     <BeginStoryboard Name="ClockStoryboard">
                         <Storyboard x:Name="Storyboard" SlipBehavior="Slip"
                                    CurrentTimeInvalidated="Storyboard_Changed">
                             <MediaTimeline BeginTime="0" Source="clock.avi"
                                 Storyboard.TargetName="meMediaElement"
                                 RepeatBehavior="Forever" />
                         </Storyboard>
                     </BeginStoryboard>
</EventTrigger.Actions>
         </EventTrigger>
         <EventTrigger RoutedEvent="Button.Click" SourceName="btnPause">
             <EventTrigger.Actions>
                 <PauseStoryboard BeginStoryboardName="ClockStoryboard" />
             </EventTrigger.Actions>
         </EventTrigger>
         <EventTrigger RoutedEvent="Button.Click" SourceName="btnResume">
             <EventTrigger.Actions>
                 <ResumeStoryboard BeginStoryboardName="ClockStoryboard" />
             </EventTrigger.Actions>
         </EventTrigger>
         <EventTrigger RoutedEvent="Button.Click" SourceName="btnStop">
             <EventTrigger.Actions>
                 <StopStoryboard BeginStoryboardName="ClockStoryboard" />
             </EventTrigger.Actions>
         </EventTrigger>
         <EventTrigger RoutedEvent="Slider.PreviewMouseLeftButtonDown"
                          SourceName="sldPosition" >
             <PauseStoryboard BeginStoryboardName="ClockStoryboard" />
         </EventTrigger>
         <EventTrigger RoutedEvent="Slider.PreviewMouseLeftButtonUp"
                          SourceName="sldPosition" >
             <ResumeStoryboard BeginStoryboardName="ClockStoryboard" />
         </EventTrigger>
     </StackPanel.Triggers>

     <!-- Media element to play the sound, music, or video file. -->
     <MediaElement Name="meMediaElement" HorizontalAlignment="Center"
                  Margin="5" MinHeight="300" Stretch="Fill"
                  MediaOpened="MediaOpened" />

     <!-- Button controls for play, pause, resume, and stop. -->
     <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
         <Button Content="_Play" Name="btnPlay" />
         <Button Content="P_ause" Name="btnPause" />
         <Button Content="_Resume" Name="btnResume" />
         <Button Content="_Stop" Name="btnStop" />
    </StackPanel>

    <!-- Slider shows the position within the media. -->
    <Slider HorizontalAlignment="Center" Margin="5"
            Name="sldPosition" Width="250"
            ValueChanged="sldPosition_ValueChanged">
    </Slider>

    <!-- Sliders to control volume and balance. -->
    <Grid>
<Grid.ColumnDefinitions>
             <ColumnDefinition Width="1*"/>
             <ColumnDefinition Width="4*"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition />
             <RowDefinition />
         </Grid.RowDefinitions>
         <TextBlock Grid.Column="0" Grid.Row="0" Text="Volume:"
                    HorizontalAlignment="Right" VerticalAlignment="Center"/>
         <Slider Grid.Column="1" Grid.Row="0" Minimum="0" Maximum="1"
                    TickFrequency="0.1" TickPlacement="TopLeft"
Value="{Binding ElementName=meMediaElement, Path=Volume, Mode=TwoWay}" />
         <TextBlock Grid.Column="0" Grid.Row="1" Text="Balance:"
                    HorizontalAlignment="Right" VerticalAlignment="Center"/>
         <Slider Grid.Column="1" Grid.Row="1" Minimum="-1" Maximum="1"
                    TickFrequency="0.2" TickPlacement="TopLeft"
Value="{Binding ElementName=meMediaElement, Path=Balance, Mode=TwoWay}" />
       </Grid>
   </StackPanel>
</Window>

The following code-behind shows the event handlers that allow the user to set the current play position using a slider and update the position of the slider to reflect the current play position:

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Recipe_12_03
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
   {
        bool ignoreValueChanged = false;

        public Window1()
        {
            InitializeComponent();
        }
// Handles the opening of the media file and sets the Maximum
        // value of the position slider based on the natural duration
        // of the media file.
        private void MediaOpened(object sender, EventArgs e)
        {
            sldPosition.Maximum =
                meMediaElement.NaturalDuration.TimeSpan.TotalMilliseconds;
        }

        // Updates the position slider when the media time changes.
        private void Storyboard_Changed(object sender, EventArgs e)
        {
            ClockGroup clockGroup = sender as ClockGroup;

            MediaClock mediaClock = clockGroup.Children[0] as MediaClock;

            if (mediaClock.CurrentProgress.HasValue)
            {
                ignoreValueChanged = true;
                sldPosition.Value = meMediaElement.Position.TotalMilliseconds;
                ignoreValueChanged = false;
            }
        }

        // Handles the movement of the slider and updates the position
        // being played.
        private void sldPosition_ValueChanged(object sender,
            RoutedPropertyChangedEventArgs<double> e)
        {
            if (ignoreValueChanged)
            {
                return;
            }

            Storyboard.Seek(Panel,
                TimeSpan.FromMilliseconds(sldPosition.Value),
                TimeSeekOrigin.BeginTime);
        }
    }
}
Controlling the playback of media files

Figure 12-3. Controlling the playback of media files

Respond When the User Clicks a UI Element with the Mouse

Problem

You need to take an action when the user clicks or double-clicks a UI element with the mouse.

Solution

Handle the MouseDown or MouseUp event inherited from System.Windows.UIElement, the MouseDoubleClick event inherited from System.Windows.Control, or the Click event inherited from System.Windows.Control.ButtonBase.

How It Works

Depending on the UI element you are working with and the kind of functionality you are trying to implement, you can handle mouse click events in a variety of ways. The MouseDown or MouseUp events are the most widely available because they are implemented by UIElement. The MouseDown event occurs as soon as the user clicks any mouse button while over a UIElement, but the MouseUp event occurs only when the user releases the button. The MouseDoubleClick event implemented by Control is raised when the user double-clicks a Control.

Note

The UIElement class also implements the MouseLeftButtonDown, MouseLeftButtonUp, MouseRightButtonDown, and MouseRightButtonUp, which as the names suggest allow you to be selective about which mouse button causes an event to be raised.

The ButtonBase class provides a special Click event support, which overrides the basic behavior of the MouseLeftButtonDown event implemented by UIElement.

The Code

The following XAML demonstrates how to hook various mouse click event handlers to a variety of control types including a Button, Label, and TextBlock (from the System.Windows.Controls namespace) as well as a System.Windows.Shapes.Rectangle:

<Window x:Class="Recipe_12_04.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_04" Height="150" Width="300">
    <UniformGrid Columns="2" Rows="2">
        <Button Content="Click" Click="Button_Click"
                MaxHeight="25" MaxWidth="100" />
        <Label Background="LightBlue" Content="Double Click"
                HorizontalContentAlignment="Center"
                MaxHeight="25" MaxWidth="100"
                MouseDoubleClick="Label_MouseDoubleClick" />
        <TextBlock Background="Turquoise" Padding="25,7"
                Text="Mouse Up" MouseUp="TextBlock_MouseUp"
                HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Canvas>
            <Rectangle Canvas.Top="15" Canvas.Left="20"
                Height="25" Width="100" Fill="Aqua"
                MouseDown="Rectangle_MouseDown" />
            <TextBlock Canvas.Top="20" Canvas.Left="40" Text="Mouse Down"
                IsHitTestVisible="False"/>
       </Canvas>
   </UniformGrid>
</Window>

The following code-behind shows the simple event handler implementations for the various mouse click events:

using System;
using System.Windows;
using System.Windows.Input;

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

        // Handles the Click event on the Button.
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Mouse Click", "Button");
        }

        // Handles the MouseDoubleClick event on the Label.
        private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Mouse Double Click", "Label");
        }

        // Handles the MouseDown event on the Rectangle.
        private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Mouse Down", "Rectangle");
        }

        // Handles the MouseUp event on the TextBlock.
        private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Mouse Up", "TextBlock");
        }
    }
}

Figure 12-4 shows the resulting window.

Handling mouse click events

Figure 12-4. Handling mouse click events

Respond When the User Clicks a UI Element in a Container with the Mouse

Problem

You need to take an action when the user clicks one of a number of UI elements held in a container.

Solution

Handle the System.Windows.UIElement.MouseUp, System.Windows.UIElement.MouseDown, or System.Windows.Control.ButtonBase.Click event in the container of the controls.

How It Works

WPF automatically bubbles the MouseDown, MouseUp, and Click events up the containment hierarchy, making it a trivial exercise to handle these events at the container level instead of that of the individual controls. All you need to do is declare an event handler of the appropriate type at the container instead of the individual control. If the container does not support the event you want to handle—such as the Click event, which is implemented by ButtonBase—you use the attached event syntax Buttonbase.Click as the event name to ensure the correct event is handled.

If the control is nested within a number of containers, the bubbled events are automatically routed up through all container levels, so you can handle the event at one or more containers where appropriate.

The Code

The following XAML demonstrates how to handle control events at the container level. To demonstrate the bubbling of events through multiple containers, when the user clicks the Rectangle in the bottom-right corner (see Figure 12-5), the event is first handled by the Canvas and then by the UniformGrid, because both containers handle the MouseDown event.

<Window x:Class="Recipe_12_05.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_05" Height="150" Width="300">
    <UniformGrid Columns="2" Rows="2" ButtonBase.Click="UniformGrid_Click"
                 MouseDown="UniformGrid_MouseDown">
        <Button Content="Button" MaxHeight="25" MaxWidth="70" Name="Button"/>
        <Label Background="LightBlue" Content="Label" Name="Label"
                HorizontalContentAlignment="Center"
                MaxHeight="25" MaxWidth="100"/>
        <TextBlock Background="Turquoise" Padding="25,7" Text="TextBlock"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Name="TextBlock"/>
        <Canvas MouseDown="Canvas_MouseDown">
            <Rectangle Canvas.Top="15" Canvas.Left="20" Fill="Aqua"
                Height="25" Width="100" Name="Rectangle"/>
            <TextBlock Canvas.Top="20" Canvas.Left="45" Text="Rectangle"
                    IsHitTestVisible="False"/>
        </Canvas>
   </UniformGrid>
</Window>

The following code-behind contains the event-handling code for the Canvas and UniformGrid controls:

using System.Windows;
using System.Windows.Input;

namespace Recipe_12_05
{

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

        // Handles the MouseDown event on the Canvas.
        private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
            FrameworkElement fe = e.OriginalSource as FrameworkElement;

            MessageBox.Show("Mouse Down on " + fe.Name, "Canvas");
       }

       // Handles the Click event on the UniformGrid.
       private void UniformGrid_Click(object sender, RoutedEventArgs e)
      {
            FrameworkElement fe = e.OriginalSource as FrameworkElement;

            MessageBox.Show("Mouse Click on " + fe.Name, "Uniform Grid");
      }

      // Handles the MouseDown event on the UniformGrid.
      private void UniformGrid_MouseDown(object sender, MouseButtonEventArgs e)
      {
            FrameworkElement fe = e.OriginalSource as FrameworkElement;

            MessageBox.Show("Mouse Down on " + fe.Name, "Uniform Grid");
    }
 }
}
Handling mouse click events at the control container

Figure 12-5. Handling mouse click events at the control container

Respond When the User Rotates the Mouse Wheel

Problem

You need to take an action when the user spins the mouse's scroll wheel.

Solution

On the control you want to respond to the mouse wheel, handle the MouseWheel event inherited from the System.Windows.UIElement class.

How It Works

When the mouse pointer is over an element and the user moves the mouse wheel, a MouseWheel event is raised on the element. To handle these events, simply attach an event handler to the MouseWheel event.

When WPF calls the event handler, it passes the handler a System.Windows.Input.MouseWheelEventArgs object that describes the mouse wheel event and the state of the mouse buttons. The Delta property of the MouseWheelEventArgs is positive if the mouse wheel is moved away from the user and negative if the mouse wheel is moved toward the user. The LeftButton and RightButton properties indicate whether the buttons are currently pressed or released using values of the System.Windows.Input.MouseButtonState enumeration.

The Code

The following XAML demonstrates how to attach mouse wheel event handlers to various UI elements. The application (shown in Figure 12-6) contains a Slider, a RichTextBox, and a Rectangle that each responds to the mouse wheel when the mouse pointer is over the element. The RichTextBox already has mouse wheel support built in to perform vertical scrolling of the content. The Slider uses a MouseWheel event handler to move the slider thumb left and right. The Rectangle uses a MouseWheel event handler to enlarge and decrease its Height and Width properties depending on whether the left mouse button is currently pressed.

<Window x:Class="Recipe_12_06.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_06" Height="300" Width="300">
    <Canvas>
        <Slider Canvas.Top="10" Canvas.Left="20" Name="sldSlider"
                Minimum="0" Maximum="1000" Value="500"
                Width="250" MouseWheel="Slider_MouseWheel"/>
        <RichTextBox Canvas.Top="50" Canvas.Left="20"
            Width="250" Height="100"
            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>
   <Rectangle Canvas.Top="160" Canvas.Left="20" Name="shpRectangle"
             Fill="LightBlue" Width="50" Height="50"
             MouseWheel="Rectangle_MouseWheel">
       </Rectangle>
   </Canvas>
</Window>

The following code-behind shows the event handlers that handle the MouseWheel event for the Slider and Rectangle:

using System.Windows;
using System.Windows.Input;

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

     // Handles the MouseWheel event on the Slider.
     private void Slider_MouseWheel(object sender, MouseWheelEventArgs e)
     {
         // Increment or decrement the slider position depending on
        // whether the wheel was moved up or down.
        sldSlider.Value += (e.Delta > 0) ? 5 : −5;
     }

     // Handles the MouseWheel event on the Rectangle.
     private void Rectangle_MouseWheel(object sender,
         MouseWheelEventArgs e)
     {
         if (e.LeftButton == MouseButtonState.Pressed)
         {
             // If the left button is pressed, increment or
             // decrement the width.

            double newWidth =
                shpRectangle.Width += (e.Delta > 0) ? 5 : −5;

            if (newWidth < 10) newWidth = 10;
            if (newWidth > 200) newWidth = 200;

            shpRectangle.Width = newWidth;
        }
        else
        {
            // If the left button is not pressed, increment or
            // decrement the height.

           double newHeight =
               shpRectangle.Height += (e.Delta > 0) ? 5 : −5;

           if (newHeight < 10) newHeight = 10;
           if (newHeight > 200) newHeight = 200;

           shpRectangle.Height = newHeight;
       }
    }
 }
}
Handling mouse wheel events

Figure 12-6. Handling mouse wheel events

Drag Items from a List and Drop Them on a Canvas

Problem

You need to allow the user to drag items from a System.Windows.Controls.ListBox to a System.Windows.Controls.Canvas.

Note

Drag and drop is relatively simple to implement in WPF but contains a lot of variations depending on what you are trying to do and what content you are dragging. This example focuses on dragging content from a ListBox to a Canvas, but the principles are similar for other types of drag and drop operations and can be adapted easily.

Solution

On the ListBox or ListBoxItem, handle the PreviewMouseLeftButtonDown event to identify the start of a possible drag operation and identify the ListBoxItem being dragged. Handle the PreviewMouseMove event to determine whether the user is actually dragging the item, and if so, set up the drop operation using the static System.Windows.DragDrop class. On the Canvas (the target for the drop operation), handle the DragEnter and Drop events to support the dropping of dragged content.

How It Works

The static DragDrop class provides the functionality central to making it easy to execute drag and drop operations in WPF. First, however, you must determine that the user is actually trying to drag something.

There is no single best way to do this, but usually you will need a combination of handling MouseLeftButtonDown or PreviewMouseLeftButtonDown events to know when the user clicks something and MouseMove or PreviewMouseMove events to determine whether the user is moving the mouse while holding the left button down. Also, you should use the SystemParameters.MinimumHorizontalDragDistance and SystemParameters.MinimumVerticalDragDistance properties to make sure the user has dragged the item a sufficient distance to be considered a drag operation; otherwise, the user will often get false drag operations starting as they click items.

Once you are sure the user is trying to drag something, you configure the DragDrop object using the DoDragDrop method. You must pass the DoDragDrop method a reference to the source object being dragged, a System.Object containing the data that the drag operation is taking with it, and a value from the System.Windows.DragDropEffects enumeration representing the type of drag operation being performed. Commonly used values of the DragDropEffects enumeration are Copy, Move, and Link. The type of operation is often driven by special keys being held down at the time of clicking, for example, holding the Control key signals the user's intent to copy (see recipe 12-9 for information on how to query keyboard state).

On the target of the drop operation, implement event handlers for the DragEnter and Drop events. The DragEnter handler allows you to control the behavior seen by the user as the mouse pointer enters the target control. This usually indicates whether the control is a suitable target for the type of content the user is dragging. The Drop event signals that the user has released the left mouse button and indicates that the content contained in the DragDrop object should be retrieved (using the Data.GetData method of the DragEventArgs object passed to the Drop event handler) and inserted into the target control.

The Code

The following XAML demonstrates how to set up a ListBox with ListBoxItem objects that support drag and drop operations (see Figure 12-7):

<Window x:Class="Recipe_12_07.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_07" Height="300" Width="300">
    <DockPanel LastChildFill="True" >
        <ListBox DockPanel.Dock="Left" Name="lstLabels">
            <ListBox.Resources>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="FontSize" Value="14" />
                    <Setter Property="Margin" Value="2" />
                    <EventSetter Event="PreviewMouseLeftButtonDown"
                        Handler="ListBoxItem_PreviewMouseLeftButtonDown"/>
                    <EventSetter Event="PreviewMouseMove"
                               Handler="ListBoxItem_PreviewMouseMove"/>
                </Style>
</ListBox.Resources>
            <ListBoxItem IsSelected="True">Allen</ListBoxItem>
            <ListBoxItem>Andy</ListBoxItem>
            <ListBoxItem>Antoan</ListBoxItem>
            <ListBoxItem>Bruce</ListBoxItem>
            <ListBoxItem>Ian</ListBoxItem>
            <ListBoxItem>Matthew</ListBoxItem>
            <ListBoxItem>Sam</ListBoxItem>
            <ListBoxItem>Simon</ListBoxItem>
        </ListBox>
        <Canvas AllowDrop="True" Background="Transparent"
                 DragEnter="cvsSurface_DragEnter" Drop="cvsSurface_Drop"
                 Name="cvsSurface" >
        </Canvas>
    </DockPanel>
</Window>

The following code-behind contains the event handlers that allow the example to identify the ListBoxItem that the user is dragging, determine whether a mouse movement constitutes a drag operation, and allow the Canvas to receive the dragged ListBoxItem content.

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

namespace Recipe_12_07
{
     /// <summary>
     /// Interaction logic for Window1.xaml
     /// </summary>
     public partial class Window1 : Window
     {
         private ListBoxItem draggedItem;
         private Point startDragPoint;

         public Window1()
         {
             InitializeComponent();
         }

         // Handles the DragEnter event for the Canvas. Changes the mouse
         // pointer to show the user that copy is an option if the drop
         // text content is over the Canvas.
         private void cvsSurface_DragEnter(object sender, DragEventArgs e)
         {
             if (e.Data.GetDataPresent(DataFormats.Text))
{
             e.Effects = DragDropEffects.Copy;
         }
         else
        {
             e.Effects = DragDropEffects.None;
        }
     }

     // Handles the Drop event for the Canvas. Creates a new Label
     // and adds it to the Canvas at the location of the mouse pointer.
     private void cvsSurface_Drop(object sender, DragEventArgs e)
     {
        // Create a new Label.
        Label newLabel = new Label();
        newLabel.Content = e.Data.GetData(DataFormats.Text);
        newLabel.FontSize = 14;

        // Add the Label to the Canvas and position it.
        cvsSurface.Children.Add(newLabel);
        Canvas.SetLeft(newLabel, e.GetPosition(cvsSurface).X);
        Canvas.SetTop(newLabel, e.GetPosition(cvsSurface).Y);
     }

     // Handles the PreviewMouseLeftButtonDown event for all ListBoxItem
     // objects. Stores a reference to the item being dragged and the
     // point at which the drag started.
     private void ListBoxItem_PreviewMouseLeftButtonDown(object sender,
         MouseButtonEventArgs e)
     {
        draggedItem = sender as ListBoxItem;
        startDragPoint = e.GetPosition(null);
     }

     // Handles the PreviewMouseMove event for all ListBoxItem objects.
     // Determines whether the mouse has been moved far enough to be
     // considered a drag operation.
     private void ListBoxItem_PreviewMouseMove(object sender,
         MouseEventArgs e)
     {
         if (e.LeftButton == MouseButtonState.Pressed)
         {
             Point position = e.GetPosition(null);

             if (Math.Abs(position.X - startDragPoint.X) >
                    SystemParameters.MinimumHorizontalDragDistance ||
                 Math.Abs(position.Y - startDragPoint.Y) >
                    SystemParameters.MinimumVerticalDragDistance)
{
                 // User is dragging, set up the DragDrop behavior.
                 DragDrop.DoDragDrop(draggedItem, draggedItem.Content,
                     DragDropEffects.Copy);
            }
         }
      }
  }
}
Dragging items from a ListBox and dropping them on a Canvas

Figure 12-7. Dragging items from a ListBox and dropping them on a Canvas

Handle Keyboard Events

Problem

You need to take an action when the user presses keys on the keyboard.

Solution

To take an action when the user presses a key, handle the PreviewKeyDown or KeyDown event. To take an action when the user releases a key, handle the PreviewKeyUp or KeyUp event. To take an action as the target element receives the text input, handle the PreviewTextInput or TextInput event.

How It Works

When the user presses a key, WPF fires the following sequence of events:

  1. PreviewKeyDown

  2. KeyDown

  3. PreviewTextInput

  4. TextInput

  5. PreviewKeyUp

  6. KeyUp

The events that begin with Preview are tunneling events that go top down through the container hierarchy to the target control. The other events are bubbling events that go from the target control up through the container hierarchy. This sequence of events going both up and down the container hierarchy provides a great deal of flexibility as to when and where you want to handle keyboard events.

The KeyUp and KeyDown events (as well as their tunneling counterparts) fire every time the user presses a key, but the PreviewTextInput and TextInput events fire only when a control receives actual input, which may be the result of multiple keystrokes. For example, pressing the Shift key to enter a capital letter would result in a KeyDown event but no TextInput event. When the user subsequently pressed the desired letter, a second KeyDown event would fire, and finally the TextInput event would fire.

Note

Some controls that do advanced text handling, such as the System.Windows.Controls.TextBox, suppress some of the keyboard events, meaning you may not always see the events when and where you would expect to handle them. If this is the case, you usually have to resort to using the Preview events.

The Code

The following XAML demonstrates how to handle keyboard events. The example handles all keyboard events raised on a TextBox control and logs them to another read-only TextBox. Figure 12-8 shows the example running after the user has pressed Shift+L and then lowercase letter g. You can see that the TextInput event is suppressed and so does not appear in the log.

<Window x:Class="Recipe_12_08.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_08" Height="300" Width="300">
    <DockPanel LastChildFill="True">
        <TextBox DockPanel.Dock="Top" FontSize="14"
                 Height="30" HorizontalAlignment="Stretch"
                 PreviewKeyDown="TextBox_KeyEvent"
KeyDown="TextBox_KeyEvent"
                 PreviewKeyUp="TextBox_KeyEvent"
                 KeyUp="TextBox_KeyEvent"
                 TextInput="TextBox_TextEvent"
                 PreviewTextInput="TextBox_TextEvent"/>
        <TextBox Name="txtLog" HorizontalAlignment="Stretch"
                 IsReadOnly="True" VerticalScrollBarVisibility="Visible"/>
    </DockPanel>
</Window>

The following code-behind contains the keyboard event handlers that write details of the events to the log:

using System;
using System.Windows;
using System.Windows.Input;

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

         // Handles all Key* events for the TextBox and logs them.
         private void TextBox_KeyEvent(object sender, KeyEventArgs e)
         {
            String msg = String.Format("{0} - {1}
",
                e.RoutedEvent.Name, e.Key);

            txtLog.Text += msg;
            txtLog.ScrollToEnd();
         }

         // Handles all Text* events for the TextBox and logs them.
         private void TextBox_TextEvent(object sender,
             TextCompositionEventArgs e)
         {
             String msg = String.Format("{0} - {1}
",
                 e.RoutedEvent.Name, e.Text);
txtLog.Text += msg;
             txtLog.ScrollToEnd();
       }
   }
}
Capturing and logging keyboard events from a TextBox

Figure 12-8. Capturing and logging keyboard events from a TextBox

Query Keyboard State

Problem

You need to query the state of the keyboard to determine whether the user is pressing any special keys.

Solution

Use the IsKeyDown and IsKeyToggled methods of the static System.Windows.Input.Keyboard class.

How It Works

The static Keyboard class contains two methods that allow you to determine whether a particular key is currently pressed or whether keys that have a toggled state (for example, Caps Lock) are currently on or off.

To determine whether a key is currently pressed, call the IsKeyDown method, and pass a member of the System.Windows.Input.Keys enumeration that represents the key you want to test. The method returns True if the key is currently pressed. To test the state of toggled keys, call the IsKeyToggled method, again passing a member of the Keys enumeration to identify the key to test.

The Code

The following XAML defines a set of CheckBox controls representing various special buttons on the keyboard. When the Button is pressed, the program uses the Keyboard class to test the state of each button and update the IsSelected property of the appropriate CheckBox (see Figure 12-9).

<Window x:Class="Recipe_12_09.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 12_09" Height="170" Width="200">
     <StackPanel HorizontalAlignment="Center">
         <UniformGrid Columns="2">
             <UniformGrid.Resources>
                 <Style TargetType="{x:Type CheckBox}">
                     <Setter Property="IsHitTestVisible" Value="False" />
                     <Setter Property="Margin" Value="5" />
                 </Style>
             </UniformGrid.Resources>
             <CheckBox Content="LeftShift" Name="chkLShift"/>
             <CheckBox Content="RightShift" Name="chkRShift"/>
             <CheckBox Content="LeftControl" Name="chkLControl"/>
             <CheckBox Content="RightControl" Name="chkRControl"/>
             <CheckBox Content="LeftAlt" Name="chkLAlt"/>
             <CheckBox Content="RightAlt" Name="chkRAlt"/>
             <CheckBox Content="CapsLock" Name="chkCaps"/>
             <CheckBox Content="NumLock" Name="chkNum"/>
         </UniformGrid>
         <Button Content="Check Keyboard" Margin="10" Click="Button_Click"/>
     </StackPanel>
</Window>

The following code-behind contains the Button.Click event that checks the keyboard and updates the CheckBox controls:

using System.Windows;
using System.Windows.Input;

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

        // Handles the Click event on the Button.
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            CheckKeyboardState();
        }

        // Checks the state of the keyboard and updates the checkboxes.
        private void CheckKeyboardState()
        {
            // Control keys.
            chkLControl.IsChecked = Keyboard.IsKeyDown(Key.LeftCtrl);
            chkRControl.IsChecked = Keyboard.IsKeyDown(Key.RightCtrl);

            // Shift keys.
            chkLShift.IsChecked = Keyboard.IsKeyDown(Key.LeftShift);
            chkRShift.IsChecked = Keyboard.IsKeyDown(Key.RightShift);

            // Alt keys.
            chkLAlt.IsChecked = Keyboard.IsKeyDown(Key.LeftAlt);
            chkRAlt.IsChecked = Keyboard.IsKeyDown(Key.RightAlt);

            // Num Lock and Caps Lock.
            chkCaps.IsChecked = Keyboard.IsKeyToggled(Key.CapsLock);
            chkNum.IsChecked = Keyboard.IsKeyToggled(Key.NumLock);
       }
   }
}
Querying keyboard state

Figure 12-9. Querying keyboard state

Suppress Keyboard and Mouse Events

Problem

You need to suppress the events raised by the keyboard or mouse.

Solution

Handle the tunneling counterpart of the event you want to suppress. In the event handler, set the Handled property of the event argument object to the value True.

How It Works

Each of the main mouse and keyboard events like MouseDown, MouseUp, KeyDown, and KeyUp has tunneling event counterparts that start off at the top of the container hierarchy and travel down to the target control. These tunneling counterparts have the prefix Preview on their name. By handling these preview events, you can intercept an event before it happens at the target control and suppress it.

Every preview event handler takes two arguments: a System.Object that contains a reference to the event sender and an object that derives from System.Windows.RoutedEventArgs that contains data specific to the event being handled. RoutedEventArgs implements a Boolean property named Handled. By setting this property to the value True in your event handler, you stop the subsequent bubbling event from firing, effectively suppressing the event.

The Code

The following XAML demonstrates how to suppress the Button.Click event by handling the PreviewMouseDown event in the container of the Button:

<Window x:Class="Recipe_12_10.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 12_10" Height="100" Width="200">
    <StackPanel Orientation="Horizontal">
        <StackPanel Orientation="Horizontal"
                    PreviewMouseDown="StackPanel_PreviewMouseDown">
            <Button Content="Blocked" Click="Button_Click"
                    Height="25" Margin="10" Width="70"/>
        </StackPanel>
        <Button Content="Not Blocked" Click="Button_Click"
               Height="25" Margin="10" Width="70"/>
    </StackPanel>
</Window>

The following code-behind shows how to suppress the Button.Click event by setting the Handled property to True in the PreviewMouseDown event handler:

using System.Windows;
using System.Windows.Input;

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

        private void Button_Click(object sender, RoutedEventArgs e)
       {
            MessageBox.Show("Button Clicked", "Button");
       }

       private void StackPanel_PreviewMouseDown(object sender,
            MouseButtonEventArgs e)
       {
            e.Handled = true;
       }
   }
}
..................Content has been hidden....................

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