Programming the Task Bar in Windows 7 with WPF 4, Part One – Thumbnail Buttons

by Alessandro Del Sole

Download the code

Introduction
Creating the Sample Project
Designing the User Interface
Implementing Command Bindings
Implementing Thumbnail Buttons
Handling Events in Managed Code
Running the Application
Conclusion

Introduction

Windows 7, the new operating system from Microsoft, provides a revisited design for the Task Bar. This item now provides several ways for the user to interact with applications. As a developer you can write native code with Visual C++ or you can take advantage of the Windows API Code Pack, which provides managed libraries for accessing the task bar and many other areas of the operating system in your managed code. Another interesting way to access the Windows 7’s Task Bar is by using the new version of Windows Presentation Foundation (WPF) which is part of the .NET Framework 4. WPF offers classes for easily manipulating the Task Bar and creating rich interactive applications. Unlike the Windows API Code Pack, which is built upon the .NET Framework 3.5 Service Pack 1 and can be used in different client scenarios (such as Windows Forms), the .NET Framework 4 offers libraries that specifically target WPF and Visual Studio 2010. WPF 4 covers the following features:

  • Thumbnail buttons that are small buttons that are added over the application’s icon and that allow interacting with the application.
  • Jump Lists which are list of items that you can easily access by right-clicking the application’s icon, such as recent files.
  • Overlay icons enabling developers to communicate the application state just overlaying the application’s icon with another one, without replacing the original icon.
  • Progress bar icons allows you to show the progress of a task inside the application’s icon.
  • Multi-touch which takes advantage of the Windows API for building touch-enabled applications.

I will describe all of these features in a series of articles. In this first article, I will describe thumbnail buttons and how they can be used for enriching your applications with interactivity features. To follow along you need to download and install Microsoft Visual Studio 2010.

Creating the Sample Project

Windows 7 introduces the concept of thumbnail buttons, which are small buttons that can be placed over the application’s icon in the Task Bar and provide interaction with the application. To show you what I mean, think of Windows Media Player. When you run Windows Media Player in Windows 7, you will notice on the Task Bar the application icon replicates buttons that are available in the user interface in the form of thumbnails, which you can use for playing media files. Figure 1 represents the thumbnails buttons in Windows Media Player.

Figure 1. Thumbnail buttons are displayed when you hover over the task bar icon for Windows Media Player.

The goal of this article is to show you how to create these thumbnail buttons by creating a simple media player with basic functionalities. We will create normal buttons on the window for selecting files to play and for controlling the media. These buttons will also be replicated onto the application’s icon in the form of thumbnail buttons, accessible from the Task Bar. With that said, run Visual Studio 2010 and create a new WPF application with Visual Basic and we can begin designing the user interface. We will reference the Task Bar in XAML, as I will describe later. For now, just focus on the look & feel of the application and on implementing the controls on the form.

Designing the User Interface

Building a basic media player requires a few controls: a MediaElement control for playing files, one button for selecting the file to play, one slider for adjusting the volume and three buttons for the canonical play/pause/stop operations. Listing 1 contains code that divides the default Grid control into two rows and places the MediaElement inside the first row.

<Window x:Class="MainWindow"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="540">
    
    <!--A simple gradient background -->
    <Window.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="Black" Offset="0" />
            <GradientStop Color="White" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>

        <!-- The MediaElement is placed in the first row (notice
             how the volume is set via data-binding)-->
        <MediaElement Name="Media1" Grid.Row="0" LoadedBehavior="Manual"
                      Volume="{Binding ElementName=VolumeSlider, Path=Value}"
                      MediaFailed="Media1_MediaFailed" >
        </MediaElement>

Listing 1. This XAML sets up the basic layout of the media player

It is worth mentioning that the MediaElement.Volume property is bound to the value of a Slider control that will be added shortly. This is just a small example of how data-binding in WPF is more than just for data access. The Binding points to the target control (ElementName) and to the value of the specified property (Path). Also, an event handler for MediaElement.MediaFailed is provided. This is required for error handling in case playing the selected media fails. Now we need to draw the buttons and the slider. It’s convenient to group all of them inside a StackPanel for the best arrangement possible. Notice that for Button controls, the code does not provide an event handler for the Click event, it instead assigns a property named Command. This is a concept related to command bindings that I will discuss in next section however you will feel at home if you are familiar with the commanding pattern. The code in Listing 2 shows how to declare the controls.

<StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button Name="PlayButton" Width="70" Height="40" Command="MediaCommands.Play"
                    Margin="5" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}" />

           <Button Name="PauseButton" Width="70" Height="40" Command="MediaCommands.Pause"
                   Margin="5" 
                   Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

            <Button Name="StopButton" Width="70" Height="40"
                    Margin="5" Command="MediaCommands.Stop"
                    Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

            <Button Name="BrowseButton" Width="40" Height="40" Command="Open"
                    Margin="5" Content="..."/>

            <!-- Setting the volume -->
            <Slider Name="VolumeSlider" Width="80" Margin="5"
                    Minimum="0" Maximum="1" Value="0.5" TickFrequency="0.1"
                    AutoToolTipPlacement="TopLeft" TickPlacement="BottomRight"/>           
        </StackPanel>
    </Grid>

Listing 2. This XAML sets up the rest of the controls and their bindings

The buttons may present a couple of peculiarities to you. The first one is that no Click event is handled, the code uses commands instead. The Command property stores the value of the command which executes the required action. The second interesting feature is related to the first three buttons and is how the Content property is set. Instead of writing text, you can point to the Command.Text property of the assigned command in order to take advantage of localized resources. For instance, if you run a localized version of the operating system different than English, you will be able to show the localized text corresponding to that command. The next step is understanding why you need commands instead of handling Click events and how you can bind commands to controls and actions using the command bindings technique.

Implementing Command Bindings

Since its first version, Windows Presentation Foundation has provided an important infrastructure for these commands. I will not discuss commands and command binding in details here but I will give you a basic understanding of what they are and how to use them. For full coverage of this topic, you can read Commanding Overview in the MSDN documentation.

There are situations where you have multiple user controls pointing to the same action. For example, you might have both a button inside a toolbar and a menu item that the user can click to open a file from disk. Instead of writing multiple event handlers for the Click event, you can map the same command to all controls that will run a specific action. A command is an object that implements the ICommand interface and using this technique is useful for another special reason; you can define your commands separately from the user interface and then bind controls to the appropriate commands. The Model-View-ViewModel pattern relies on this technique. WPF ships with a number of default commands, divided into different categories and targeting different scenarios. For example, the System.Windows.Input.ApplicationCommands class defines a number of commands for opening, saving and printing files while System.Windows.Documents.EditingCommands defines commands for text editing common operations. You can also create your custom commands, by implementing the ICommand interface. With regard to the sample application described in this article, we can take advantage of the System.Windows.Input.MediaCommands class, which defines commands for working with media files. Basically you define a CommandBindings section within the panel that contains the user controls, such as the Window object. This XAML can be also placed inside a StackPanel, a Grid or in the Application.Xaml file which is useful for exposing commands to the entire application. Within this section, you specify a CommandBinding object for each command you need to map to a control and specify the event handlers for the Executed and CanExecute events. I will describe in more details both events later in this article. For now, take a look at the code in Listing 3 which defines the command bindings for our media player application.

<!-- Assigning event handlers to commands-->
    <Window.CommandBindings>
        <CommandBinding Command="MediaCommands.Play" x:Name="PlayCommand" 
                        Executed="PlayCommand_Executed"
                        CanExecute="PlayCommand_CanExecute"/>
        <CommandBinding Command="MediaCommands.Stop" x:Name="StopCommand"
                        Executed="StopCommand_Executed"
                        CanExecute="StopCommand_CanExecute"/>
        <CommandBinding Command="MediaCommands.Pause" x:Name="PauseCommand"
                        Executed="PauseCommand_Executed"
                        CanExecute="PauseCommand_CanExecute"/>
        <CommandBinding Command="Open" x:Name="OpenCommand"
                        Executed="OpenCommand_Executed"
                        CanExecute="OpenCommand_CanExecute"/>
    </Window.CommandBindings>

Listing 3. Assigning event handlers to commands

Each CommandBinding object points to the specified Command and binds both the Executed and CanExecute event handlers. Basically buttons are bound to the specified command (Command property) and each command is bound to event handlers via command bindings. After defining these special objects, it is time to begin taking a look at how WPF 4 makes interacting with the Task Bar in Windows 7 really easy.

Implementing Thumbnail Buttons

Every time you want to interact with the Task Bar, you need to get a reference to it. To accomplish this you use the System.Windows.Shell.TaskbarItemInfo class, which also has a XAML counterpart that you can access via the System.Windows.Windows.TaskbarItemInfo property. Once you get the reference, you can access a number of features in the Task Bar. In order to be able to add thumbnail buttons to your window, you need to define a ThumbButtonInfos section nesting a ThumbButtonInfoCollection property which is just a collection of buttons. Each button is then represented by an instance of the ThumbButtonInfo object. The code in Listing 4 shows how to add three thumbnail buttons to your application:

<Window.TaskbarItemInfo>
        <TaskbarItemInfo Description="Control your media" ThumbnailClipMargin="5">
            <TaskbarItemInfo.ThumbButtonInfos>
                <ThumbButtonInfoCollection>
                    <ThumbButtonInfo x:Name="ThumbPlayButton" Command="MediaCommands.Play" 
                                     DismissWhenClicked="False" CommandTarget="{Binding ElementName=PlayButton}"
                                     Description="Play" 
                                     ImageSource="/ProgrammingWindows7TaskBar_ThumbButtons;component/Images/PlayHS.png" />
                    <ThumbButtonInfo x:Name="ThumbStopButton" Command="MediaCommands.Stop" 
                                     DismissWhenClicked="False" CommandTarget="{Binding ElementName=StopButton}"
                                     Description="Stop" ImageSource="/ProgrammingWindows7TaskBar_ThumbButtons;component/Images/StopHS.png" />
                    <ThumbButtonInfo x:Name="ThumbPauseInfo" Command="MediaCommands.Pause" 
                                     DismissWhenClicked="False" CommandTarget="{Binding ElementName=PauseButton}"
                                     Description="Pause" ImageSource="/ProgrammingWindows7TaskBar_ThumbButtons;component/Images/PauseHS.png" />
                </ThumbButtonInfoCollection>
            </TaskbarItemInfo.ThumbButtonInfos>
        </TaskbarItemInfo>
    </Window.TaskbarItemInfo>

Listing 4. Setting up the thumbnail buttons to display on the Windows 7 task bar

The Description property will show a tooltip when the mouse pointer passes over the thumbnail, while ThumbnailClipMargin allows setting a margin. DismissWhenClicked is used to specify that a button is available only until it is clicked. Also notice how the CommandTarget property binds the thumbnail button to the related one on our WPF window. This is important because if you do not assign this property, the command will not work. Also keep in mind that only a single ThumbButtonInfoCollection can be added and that you can only add a maximum of 7 ThumButtonInfo objects. Each thumbnail button exposed by the task bar works similar to a normal Button control. It exposes a Click event and a Command which can be assigned with the appropriate command defined in XAML code like we previously assigned to the buttons on our form. Notice that these particular kinds of thumbnail buttons cannot show text; they can only show images, so you have to assign the ImageSource property. The sample code uses images available in the Visual Studio 2010 Image Library that ships with with Visual Studio 2010. You can find this library in the %Program Files%\Microsoft Visual Studio 10.0\Common 7\VS2010ImageLibrary folder. By default the Image Library is offered as a .zip archive so you will need to decompress it and search for your favorite images by browsing the extracted subfolders. These images must be 16 x 16 pixels. 

At this point we have completed the UI portion of the application. Figure 2 shows how the UI looks inside the Visual Studio designer. Now we can switch to the managed code editor in order to write event handlers.

Figure 2. The main window of the application inside the designer

Handling Events in Managed Code

In order to complete our application, we need to handle events in the code-behind Visual Basic file. Basically there are different kinds of events that must be handled:

  • events from the MediaElement control, such as MediaFailed which is raised in case attempting to play the media file fails;
  • Executed and CanExecute events for each command bound to a particular control (in our scenario, both buttons and thumbnails buttons).

Listing 5 shows how to handle the MediaFailed event.

Class MainWindow

    'Stores the file name to play
    Private sourceMedia As String = String.Empty
    'Stores the state of media reproduction
    Private isPlaying As Boolean = False


    'In case opening the media file fails
    Private Sub Media1_MediaFailed(ByVal sender As System.Object,
                                   ByVal e As System.Windows.ExceptionRoutedEventArgs)
        MessageBox.Show(e.ErrorException.Message)
    End Sub

Listing 5. Handling events in managed code

The delegate signature for the MediaElement.MediaFailed event requires an argument e of type System.Windows.ExceptionRoutedEventArgs, which exposes the ErrorException property offering useful information to understand what error occurred. This property exposes another property named Message which contains a meaningful text regarding the error.

The next step is handling Executed and CanExecute events. Each CanExecute event handler specifies a condition that determines whether the bound command can be executed. The bound command (and therefore the related control) is disabled until CanExecute is set to True. For more information see the Commanding Overview document in the MSDN Library.  So while CanExecute is False the related command and control are disabled and the Executed event handler will not be raised. Once the condition changes to True, the bound command is enabled and the user can execute the associated command which will raise the Executed event. In our example, the buttons for controlling the media (play/pause/stop) are disabled if no media file has been selected, and the browse button is disabled if the media file is currently being played. Code in Listing 6 show an example of handling both the CanExecute and Executed events.

'Setting the condition that makes commands executable
    'if evaluated as true
    Private Sub PlayCommand_CanExecute(ByVal sender As System.Object,
                                       ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
        e.CanExecute = Me.Media1.Source IsNot Nothing
    End Sub

    Private Sub StopCommand_CanExecute(ByVal sender As System.Object,
                                       ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
        e.CanExecute = Me.Media1.Source IsNot Nothing
    End Sub

    Private Sub PauseCommand_CanExecute(ByVal sender As System.Object,
                                        ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
        e.CanExecute = Me.Media1.Source IsNot Nothing
    End Sub

    'Executing command actions
    Private Sub PlayCommand_Executed(ByVal sender As System.Object,
                                     ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
        Me.Media1.Play()
        Me.isPlaying = True    End Sub

    Private Sub StopCommand_Executed(ByVal sender As System.Object,
                                    ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
        Me.Media1.Stop()
        Me.isPlaying = False
    End Sub

    Private Sub PauseCommand_Executed(ByVal sender As System.Object,
                                      ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
        Me.Media1.Pause()
        Me.isPlaying = False
    End Sub

    Private Sub OpenCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        Dim dialog As New Microsoft.Win32.OpenFileDialog
        With dialog
            .Title = "Select a media file"
            .Filter = "Avi & Wmv|*.avi;*.wmv|All files|*.*"
            If .ShowDialog = True Then
                'assigns the file name to the appropriate field
                Me.sourceMedia = .FileName
                'sets the MediaElement.Source property, which is of type Uri
                Me.Media1.Source = New Uri(sourceMedia, UriKind.RelativeOrAbsolute)
            End If
        End With
    End Sub

    Private Sub OpenCommand_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
        e.CanExecute = Not Me.isPlaying
    End Sub

End Class

Listing 6. Handling both the CanExecute and Executed events

You assign to the e.CanExecute property (exposed by the CanExecuteRoutedEventArgs class) the Boolean value True in order to mark commands as executable. The Executed events are then called when the user actually clicks the button.

Running the Application

When you run the application you will be able to play your favorite music or videos inside the user interface, but the real magic happens when you hover the mouse over the application icon in the Task Bar. Figure 3 shows the result of our work.

Figure 3. The custom media player is running and thumbnail buttons are visible in the Task Bar

Notice that the thumbnail buttons will remain disabled until you select a valid media file. This behavior is also visible on the main user interface and works because of the command bindings technique. It is worth mentioning that if you run such an application on Windows Vista or Windows XP, the .NET Framework will simply ignore the code related to the Task Bar and the application will work normally.

Conclusion

Windows 7 provides features that can dramatically enhance the user experience in your applications. With WPF 4, programming the Task Bar is a simple task. If you are interested deeper into building applications for Windows 7, you should check out the free Windows 7 Training Kit for Developers from Microsoft, which ships with demos, labs and videos that will help you get started in development for the new operating system with both Visual Studio 2008 and 2010 (including WPF 4).

About the author

Alessandro Del Sole is a Microsoft Visual Basic MVP and Team Member in the Italian “Visual Basic Tips & Tricks” Community. He writes many Italian and English language community articles and books about .NET development and is the author of the book “Visual Basic 2010 Unleashed”. He also enjoys writing freeware and open-source developer tools. You can visit Alessandro’s Italian language blog or his English language blog.