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
TabItem
s 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 TabItem
s 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:
- Add TabItemGeneratorBehavior.cs file to the project
- 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.