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.




April 10, 2008 at 9:17 am |
Oh yea!! Very cool. This is a great idea!
Keep it up.
Josh
April 10, 2008 at 9:18 am |
Thanks Josh!
April 10, 2008 at 9:38 am |
Very nice Karl. Yup – MDI refuses to fade away (hey – that’s an idea; fading MDI tab switching).
April 10, 2008 at 9:48 am |
Pete,
Thank you!
I have been thinking about animation in business apps. My first thought was no, but the more I watch and learn, animation can help users see when the UI is changing, notifiying or transsitioning.
So, I’ll add the animation to the WPF Business Application Series and make it optional by user.
Cheers,
Karl
April 10, 2008 at 9:53 am |
That’s cool Karl. It’s tempting to keep with the old way of doing things, but with the power of WPF there’s no reason that we can’t provide visual cues to the user to show that things are happening.
April 10, 2008 at 9:58 am |
Pete,
That is the same conclusion I came to. I like the term you used to describe the animation, “visual cues.” I’ll remember that for training & sales sessions. “Our application provides configurable non-modal visual cues to users….”
Cheers,
Karl
April 10, 2008 at 1:19 pm |
nice stuff dude!!!! Brilliant
April 10, 2008 at 1:24 pm |
Marlon,
Thank you!
Cheers,
Karl
April 10, 2008 at 4:31 pm |
Have you thought about swapping out the ListBox’s ItemsPanel with a Panel3D?
April 10, 2008 at 4:34 pm |
Sure did. I was going to try your new awesome control and stack’m up like Vists does with the WINDOWS button + TAB.
April 22, 2008 at 4:49 pm |
This is a very nice article and the example is great. As a complete WPF newbie (and a relative c# newbie, to boot) I decided to spend the afternoon converting your example into c# code. This was a very helpful article for “getting my hands dirty” with WPF. Thanks!
April 23, 2008 at 4:57 pm |
Fantastic! So glad you can get something from the posts. Don’t you just love WPF!!
Cheers,
Karl
May 14, 2008 at 7:43 pm |
Namaste! WPF Guru, I’m a newbies in WPF and I like your code… wow it’s great… anyway do have a C# version of “WPF MDI Task Switching”? may share it to me? thanks in advance
May 14, 2008 at 7:47 pm |
Thank you very much for your kind words. Glad you like this.
I’m working on the next article in the WPF Business Application Series ( Part 4 ) and after this is done, we’ll start the translation into C#.
In the mean time, you can use this code converter, http://labs.developerfusion.co.uk/convert/csharp-to-vb.aspx. The code is pretty small so it shouldn’t be too much work.
Best to you!
Cheers,
Karl
December 23, 2008 at 11:12 am |
has anyone have the source code for this projectin c#? if you do could you email me the project and source code at steve_44@inbox.com
I tried to convert the source code to c# but the converter does not work successfully.
If anyone know of a link where I can download the source code for the project in c# could you email me the link
December 23, 2008 at 12:38 pm |
Steve,
I don’t know if this has been translated to C#. You can always just reference the .dll consume from other C# projects.
Cheers,
Karl
April 23, 2009 at 10:55 am |
Karl,
I’m trying to use this code in a project that also uses Ocean Framwork. In my main application I have a TabControl which ItemSource property is bound to an ObservableCollection just like you did in the sample project WPFLOBMVVM. Every item in the collection has a DataTemplate that renders the view model. This causes an error when I try to create the visual brushes because the Items collection of the TabControl does not contain TabItems from which I can extract the Content property but contains the list of my current opened view models. Is there a way I can solve this issue?
Thanks,
Nicola
May 23, 2009 at 10:19 am |
Nicola,
You are correct. I used Mole to view the VisualTree and there is nothing to look at.
I’m over committed the next two weeks, after that I’ll try and sort this out. I’ll have to look at another approach.
Thank you for bringing this up.
Cheers,
Karl