This is the next sample in the WPF Sample Applications Series. The purpose of the Sample Series is to provide concise code solutions for specific programming tasks. This sample provides a brief description of the problem, the solution and full source code.
I’ve been working on the WPF Business Application Series and wanted to add Windows style task switching to my MDI applications. Task switching is familiar to Windows users and can help them find a window when they have several open at once. Also this type of feature “shows” well in demonstrations.
From the above image, you can see that three MDI forms were opened up and then the user opened the task switching dialog by pressing CTRL+TAB. The user can then continue to cycle through the open forms by pressing CTRL+TAB. The user can also select a form by clicking the name of the form with the mouse.
Once a form is opened, you can RIGHT CLICK on the form and select Close Tab from the context menu to close the form.
UI Demonstration Video

The video link require Microsoft Silverlight 1.0. If you do not have it, you will be prompted to install it when you click one of the links. After the short installation is completed, close the browser window that you did the install in and re-click the video you want to watch. You can also download Silverlight here. Windows XP or Vista required.
Program Flow
When the application is started up, the menu is loaded from data. In this demo application, it is loaded from strings. In a real world application, the menu data will come from a database, with menu options selected based on the users security content.
When a menu item is clicked, the window OnPetMenuClick event handler dynamically loads the required UserControl into a TabItem that is then loaded into the TabControl and displayed to the user.
The user then decides to use the task switching feature and presses the CTRL+TAB keystroke combination. This is trapped by the window PreviewKeyDown event handler and the task switching UserControl is brought into view. When this happens, focus is also moved to the task switching UserControl and is trapped within the control until it closes. The user can use CTRL+TAB to cycle through their forms, find the one they want and select it. After selecting the form, the task switcher closes and they are brought to the form they selected.
Program Code
<DockPanel> <Border DockPanel.Dock="Top"> <Grid> <TabControl x:Name="tcOpenForms" SelectedIndex="0" TabStripPlacement="Bottom" /> <local:TaskSwitcherControl x:Name="ucTaskSwitcherControl" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"> <local:TaskSwitcherControl.BitmapEffect> <OuterGlowBitmapEffect GlowSize="8" GlowColor="#FF5A5A5A" /> </local:TaskSwitcherControl.BitmapEffect> </local:TaskSwitcherControl> </Grid> </Border> </DockPanel>
The above code snippet is from the application main window. The TabControl and TaskSwitcherControl occupy the same space in the parent Grid. This is how to achieve the modal popup look that the task switcher has without actually opening a modal dialog box. Normally the TaskSwitcherControl is collapsed. When the user pressed CTRL+TAB, the Visibility property is changed to Visible and the control overlays the TabControl.
Private Sub ApplicationMainWindow_PreviewKeyDown(ByVal sender As Object, _ ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.PreviewKeyDown If Me.tcOpenForms.Items.Count > 0 AndAlso Me.ucTaskSwitcherControl.Visibility = _ Windows.Visibility.Collapsed AndAlso e.Key = Key.Tab AndAlso _ e.KeyboardDevice.Modifiers = ModifierKeys.Control Then Me.ucTaskSwitcherControl.Visibility = Windows.Visibility.Visible Me.ucTaskSwitcherControl.Show(Me.tcOpenForms) e.Handled = True End If End Sub Private Sub ApplicationMainWindow_PreviewKeyUp(ByVal sender As Object, _ ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.PreviewKeyUp If Me.ucTaskSwitcherControl.Visibility = Windows.Visibility.Visible AndAlso _ e.KeyboardDevice.Modifiers <> ModifierKeys.Control Then Me.ucTaskSwitcherControl.Hide() End If End Sub
The above code shows how the CTRL+TAB key combination is trapped and handled. If trapped then the task switching UserControl’s Visibility property is set to Visible and the Show method is called on the control.
When the user closes the task switcher its Hide method is called.
Task Switcher Code
Imports System.Collections.ObjectModel Partial Public Class TaskSwitcherControl #Region " Declarations " Private _objTabControl As TabControl Private _objTabItems As ObservableCollection(Of TabItemMetaData) #End Region #Region " Methods " Private Sub TaskSwitcherControl_PreviewKeyDown(ByVal sender As Object, _ ByVal e As System.Windows.Input.KeyEventArgs) _ Handles Me.PreviewKeyDown If e.Key = Key.Tab AndAlso e.KeyboardDevice.Modifiers = ModifierKeys.Control Then e.Handled = True If Me.lbFormList.Items.Count - 1 = Me.lbFormList.SelectedIndex Then Me.lbFormList.SelectedIndex = 0 Else Me.lbFormList.SelectedIndex += 1 End If End If End Sub Private Sub lbFormList_SelectionChanged(ByVal sender As Object, _ ByVal e As System.Windows.Controls.SelectionChangedEventArgs) _ Handles lbFormList.SelectionChanged If Me.lbFormList Is Nothing OrElse Me.lbFormList.Items.Count = 0 OrElse _ Me.lbFormList.SelectedIndex = -1 Then Exit Sub End If Me.tbCurrentApplication.Text = CType(CType(_objTabControl.Items( _ Me.lbFormList.SelectedIndex), TabItem).Tag, TabItemMetaData).ApplicationName Me.rectFormPreview.Fill = New VisualBrush(CType(CType(_objTabControl.Items( _ Me.lbFormList.SelectedIndex), TabItem).Content, UIElement)) End Sub Friend Sub Hide() _objTabControl.SelectedIndex = CType(CType(_objTabControl.Items( _ Me.lbFormList.SelectedIndex), TabItem).Tag, TabItemMetaData).TabControlIndex Me.Visibility = Windows.Visibility.Collapsed _objTabItems = Nothing Me.lbFormList.DataContext = Nothing End Sub Friend Sub Show(ByVal objTabControl As TabControl) _objTabControl = objTabControl If objTabControl.Items.Count < 1 Then Hide() Exit Sub End If _objTabItems = New ObservableCollection(Of TabItemMetaData) For intX As Integer = 0 To objTabControl.Items.Count - 1 Dim obj As TabItem = objTabControl.Items(intX) Dim objTabItemMetaData As TabItemMetaData = _ CType(obj.Tag, TabItemMetaData) objTabItemMetaData.TabControlIndex = intX _objTabItems.Add(objTabItemMetaData) Next Me.lbFormList.DataContext = _objTabItems Me.UpdateLayout() Me.lbFormList.SelectedIndex = objTabControl.SelectedIndex Me.lbFormList.Focus() End Sub #End Region End Class
The PreviewKeyDown handler is used to cycle through the forms in the task switcher control.
When the ListBox selection is changed, I’m painting the visual of the selected TabItem on the Rectangle and setting the text string at the bottom of the control.
The Hide method is simple clean up.
The Show method is the work horse. It is here that a list of current TabItems is created and bound to the ListBox in the task switching control. The hardest part of this entire application was figuring out to get focused moved to the task switching control and then keep it there, so that when the user is pressing the CTRL+TAB keys, focus does not leave the control. Focus is then confined to the task switcher until it is closed.
<UserControl x:Class="TaskSwitcherControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="clr-namespace:MDITaskSwitchingSample" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="550" Height="320"> <Border Width="Auto" Height="Auto" CornerRadius="20,20,20,20" BorderThickness="1,1,1,1" BorderBrush="#FF333333"> <Border.Background> <LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1"> <GradientStop Color="#FFD8ECFF" Offset="0" /> <GradientStop Color="#FF0161C2" Offset="1" /> <GradientStop Color="#FFC3DFFB" Offset="0.817" /> </LinearGradientBrush> </Border.Background> <Grid Width="Auto" Height="Auto" KeyboardNavigation.ControlTabNavigation="Cycle"> <Grid.Resources> <local:TaskSwitcherTextConverter x:Key="taskSwitcherTextConverter" /> <LinearGradientBrush x:Key="BrushSelectedTabItem" EndPoint="0.5,0" StartPoint="0.5,1"> <GradientStop Color="#FF759AC0" Offset="0" /> <GradientStop Color="#FFC5E2FF" Offset="1" /> <GradientStop Color="#FFC3DFFB" Offset="0.86" /> </LinearGradientBrush> <!-- Makes the border of ListBoxItems with keyboard focus invisible --> <Style x:Key="MyFocusVisualStyle" TargetType="Control"> <Setter Property="BorderBrush" Value="Transparent" /> </Style> <Style x:Key="{x:Type ListBoxItem}" TargetType="ListBoxItem"> <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisualStyle}" /> <Style.Resources> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" /> <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" /> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="{x:Static SystemColors.ControlTextColor}" /> </Style.Resources> </Style> <!-- Makes the border of TextBlock with keyboard focus invisible --> <Style x:Key="{x:Type TextBlock}" TargetType="TextBlock"> <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisualStyle}" /> </Style> <Style x:Key="{x:Type ListBox}" TargetType="ListBox"> <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisualStyle}" /> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.538*" /> <ColumnDefinition Width="0.462*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.135*" /> <RowDefinition Height="0.835*" /> <RowDefinition Height="25" /> </Grid.RowDefinitions> <TextBlock x:Name="tbTitle" HorizontalAlignment="Stretch" VerticalAlignment="Center" Text="Select Task" TextWrapping="Wrap" Margin="10,0,10,0" FontSize="20" Foreground="#FFFFFFFF" FontWeight="Normal" Grid.ColumnSpan="2" /> <ListBox ItemsSource="{Binding}" SelectionMode="Single" BorderBrush="{x:Null}" Background="{x:Null}" Margin="10,10,10,1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Row="1" x:Name="lbFormList"> <ListBox.ItemTemplate> <DataTemplate> <Border BorderThickness="1" x:Name="bd" Padding="2" Margin="5,0,0,0"> <TextBlock x:Name="tb" FontSize="14" Focusable="True" Text="{Binding Converter= {StaticResource taskSwitcherTextConverter}}" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" /> </Border> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True"> <Setter TargetName="bd" Property="Background" Value="{StaticResource BrushSelectedTabItem}" /> <Setter TargetName="bd" Property="BorderBrush" Value="LightBlue" /> <Setter TargetName="tb" Property="Foreground" Value="White" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ListBox.ItemTemplate> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel ItemHeight="30" ItemWidth="130" Background="{x:Null}" Orientation="Horizontal" Width="265" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Row="1" Grid.Column="1" Stretch="Uniform" x:Name="rectFormPreview" Margin="10,10,10,1" /> <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Row="2" Grid.ColumnSpan="2" CornerRadius="0,0,20,20" BorderThickness="1,1,1,1" Background="#FF8CD0FF"> <TextBlock Text="TextBlock" TextWrapping="NoWrap" Margin="13,0,13,0" VerticalAlignment="Center" x:Name="tbCurrentApplication" TextTrimming="CharacterEllipsis" Foreground="#FF000000" /> </Border> </Grid> </Border> </UserControl>
There are three areas that need some explanation. First is the attached property KeyboardNavigation.ControlTabNavigation that is applied to the Grid control. This property ensures that keyboard navigation focus remains within the Grid. This allows the user to cycle through the forms in the task switcher without leaving the control.
Second is the Styles that target the ListBoxItem. These remove the default colors of the ListBoxItem if it selected, has focus or is selected and loses focus. Bookmark this code or else copy to your code favorites as you will need this in the future. This is code that gives the nice clean look to our UI, even though the user is tabbing and selecting items in a ListBox, no focus visual styles or default colors appear. There are also styles for the TextBlocks and ListBox to remove the FocusVisualStyles from these controls too.
Third, if you study this ListBox code, you’ll see that I have an ItemTemplate and ItemsPanel defined.
The ItemTemplate defines how each data item that is bound the ListBox is rendered. The trigger fires when the ListBoxItem is the selected item. This is how I rendered the nice coloring around the forms name in the task switcher.
The ItemsPanel has been set to a WrapPanel. This panel renders each child the same size, which works great for our application.
Close
Source Code: After downloading the source code you MUST change the file extension from .zip.DOC to .zip. This is a requirement of WordPress.com.
Hope you can learn just a little bit more about WPF from this article and the Sample Series.
Just a grain of sand on the worlds beaches.


