Click here to Skip to main content
Click here to Skip to main content

Persist the Visual Tree when switching tabs in the WPF TabControl (Optimized)

, 9 Apr 2012
Rate this:
Please Sign up or sign in to vote.
This is an alternative for "Persist the Visual Tree when switching tabs in the WPF TabControl".

Introduction

Once upon a time I happened to work on a project which used TabControl to switch from one view to the next. All was well except it took forever to switch between views. Each view recreated the entire visual tree every time it became active. A quick search brought me to this excellent article by Jason Ching: Persist the Visual Tree when switching tabs in the WPF TabControl. It did precisely what I needed. I started to use his code and performance increased dramatically but there was still room for improvement. This article explains what could be done to already good code to make it even better.

Background

The idea behind Jason’s implementation in simple words could be described as this: Create behaviour, attach it to the TabControl where it waits for a new source items to be created (or whole new Items Source container attached). Once new item is created the behaviour creates a TabItem instance and wraps source item in it. This simple trick prevents TabControl from discarding TabItem’s instance and persists visual tree of the Tab.

Implementation

The behaviour is implemented by three classes: PersistTabBehavior, PersistTabItemsSourceHandler, and PersistTabSelectedItemHandler.

  • PersistTabBehavior – handles attached properties and maintains two static dictionaries where instances of Tab Controls are associated with Items Source and Selected Item handlers.
  • PersistTabSelectedItemHandler – translates internal TabControl selection into external selection.
  • PersistTabItemsSourceHandler – handles creating and manipulating TabItems associated with data source items.

For more details please consult original article.

Improvements

First thing I thought would improve performance is if we could get rid of the dictionaries and lookup process associated with them. Instead the instance of the behaviour should hold reference to both the TabControl as well as reference to items source.

I also thought that three different classes for one simple control behaviour is a bit of an overkill. If all the references are held in one place it is much easier to access only one instance of the object as opposed to multiple so I’ve merged all these classes into only one.

Referring external items source container is easy; we could simply store it in one of the fields.

The challenge is in making TabControl to reference correct instance of the handler which responds to changes in internal TabControl.SelectedItem property. TabControl.SelectedItem is an attached property of the TabControl class. So we could solve reference problem by using WPF Binding, after all Bindings where designed just for that purpose. Binding contains references to both the target property instance as well as reference to source instance. This allows us to retrieve correct instance just by examining the Binding object. This effectively eliminating a need for the dictionaries.

Now any time either TabItemGeneratorBehavior.SelectedItemProperty or TabItemGeneratorBehavior.ItemsSourceProperty is attached to the control we create instance of TabItemGeneratorBehavior and initialize fields _innerSelection, _tabControl, _itemsSource with references to respective objects, attach to required events and etc.

At the same time we create binding between TabControl.SelectedItem and TabItemGeneratorBehavior.SelectedTabItem public property of the behaviour:

_tabControl = tab;
_tabControl.Loaded += OnTabLoaded;
_tabControl.SetBinding(TabControl.SelectedItemProperty,
                       new Binding("SelectedTabItem") { Source = this });

After that the behaviour operates exactly as the original.

Using the Code

I’ve provided a sample project to demonstrate behaviour's functionality.

The screen is split with two TabControls populating the area. The control on the left is not using the behaviour and control on the right does. The view area of the tab displays the unique ID of the TabItem instance that is associated with the active tab.

As you can see on the left, TabControl reuses same instance of the TabItem class to display all of the items. So each time new tab is displayed all of the visual items on that tab are discarded and new visuals for selected item are created.

On the right each item has its own instance of the TabItem. These TabItems are preserved when control switches between tabs and visual tree is not recreated.

Using the original sample

If you would like to try this implementation with sample project associated with original article you need to follow these steps:

  1. Add TabItemGeneratorBehavior.cs file to the project
  2. Modify MainWindow.xaml file to include reference to the new behaviour:
  • Add xmlns:beh="clr-namespace:System.Windows.Controls" to the <Window…> tag.
  • Replace b:PersistTabBehavior with beh:TabItemGeneratorBehavior on the <TabControl... tag.

History

  • 04/05/2012 - Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Eugene Sadovoi
Software Developer (Senior)
United States United States
Senior Software Engineer with over 20+ years of experience in variety of technologies, development tools and programming languages.
 
Microsoft Certified Specialist programming in C#, JavaScript, HTML, CSS

Comments and Discussions

 
QuestionProblem when using Validation.ErrorTemplate PinmemberJiri V13-Nov-13 2:54 
QuestionPeer review request PinmemberIvan Krivyakov11-Sep-12 16:04 
AnswerRe: Peer review request PinmemberEugene Sadovoi27-Feb-13 7:35 
GeneralThank you! PinmemberMatt Korth26-Jul-12 2:48 
GeneralRe: Thank you! PinmemberEugene Sadovoi26-Jul-12 8:28 
BugRe: Thank you! PinmemberMatt Korth14-Nov-12 9:20 
You're welcome. Smile | :)
 
I did find one minor issue that appears to happen if you've got your source collection already populated, and a selected item already set, when you bind to the tab control with TabItemGeneratorBehavior. It looks like in that case, SelectedItem was being bound before ItemsSource. This meant that while the tabs were created, no tab was selected (since there was no TabItem with a DataContext that matched the value of SelectedItem at the time SelectedItem was bound).
 
To fix it, I stole the code from OnSelectedItemPropertyChanged, and added it to the end of OnTabLoaded with some minor changes:
protected virtual void OnTabLoaded(object sender, RoutedEventArgs e)
{
    // ...

    object selectedItem = TabItemGeneratorBehavior.GetSelectedItem(sender as TabControl);
    if (selectedItem != null)
    {
        TabItemGeneratorBehavior instance = GetHandler(sender as TabControl);
 
        instance._innerSelection =
            instance._tabControl.Items.Cast<TabItem>().FirstOrDefault(t => selectedItem.Equals(t.DataContext));
 
        instance.PropertyChanged(instance, new PropertyChangedEventArgs("SelectedTabItem"));
    }
}

QuestionThanks for the article PinmemberJasonChing17-Jul-12 22:05 
AnswerRe: Thanks for the article PinmemberEugene Sadovoi18-Jul-12 4:36 
Generalreally useful Pinmembersthotakura3-Jul-12 5:21 
GeneralRe: really useful PinmemberEugene Sadovoi20-Jul-12 2:33 
GeneralMy vote of 5 PinmemberManele4-Jun-12 5:32 
GeneralRe: My vote of 5 PinmemberEugene Sadovoi4-Jun-12 8:08 
QuestionAh the old WPF TabControl love it PinmvpSacha Barber9-Apr-12 7:00 
AnswerRe: Ah the old WPF TabControl love it PinmemberEugene Sadovoi9-Apr-12 7:13 
AnswerRe: Ah the old WPF TabControl love it PinprotectorPete O'Hanlon9-Apr-12 9:38 
AnswerRe: Ah the old WPF TabControl love it Pinmemberguton12-Aug-12 8:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 9 Apr 2012
Article Copyright 2012 by Eugene Sadovoi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid