Introduction

There are two different behaviors in the WPF TabControl. When you are binding the tab items from the ViewModel to the ItemsSource property, the TabControl will create a new visual tree every time you switch between tabs. However, when you are adding a TabItem to the Items collection directly, the visual tree will be persisted when the tab is not active. While the databinding behavior is expected, this does create some issues:

Slow rendering. You can obviously see the delay when you have a heavy tab.

Visual change not persisted. If you change a column width in the tab, you will lose it after the visual tree is recreated.

In this article, I assume you have a basic understand about MVVM and attached properties. There is lots of material you can find on the Internet about these topics. To understand what attached behaviors is, check out Josh Smith's article.

Workaround

Don't use ItemsSource. Instead, have a wrapper convert your source to a TabItem and add it to the TabControl. In this case, you will still have all the cool things from data binding, while keeping the desired behavior.

The idea is simple, and there are lots of ways to implement this. You can extend the TabControl class, or just do it in code-behind. The way I have done is using the attached behaviour pattern. In this way, we can keep the code out from XAML and code-behind, which means it can be reused. Also, doing this as an attached pattern would be more flexible than overriding the TabControl class.

The Demo

The idea of this demo is to show the differences between using PersistTabBehavior and the original ItemsSource.

This demo contains a WPF window with two TabControls. Each TabControl has two tabs, and each tab has a big DataGrid for the purpose of slowing down the rendering. The top control uses attached properties explained in this article. The bottom control is just a regular TabControl. You can see that the performance of the top TabControl is better when switching between tabs.

As you can see, we have replaced ItemsSource and SelectedItem with the new attached properties: PersistTabBehavior.ItemsSource and PersistTabBehavior.SelectedItem. This is the beauty of attached behaviors. It is not necessary to create an extended TabControl class. We have attached our customized behavior to the original TabControl, and all the implementation is done in a separate class.

Part 1 - PersistTabBehavior.ItemsSource

Since the nature of attached properties is static, it gets complicated to manage all the events and objects when you have more than one TabControl, and that TabControl may be removed from the windows before the application exits. Therefore, I have created a separate class, PersistTabItemsSourceHandler, to handle that.

All the PersistTabItemsSourceHandler instances will be saved to the ItemSourceHandlers dictionary. And it will be disposed after the TabControl is unloaded from the UI.

Adding and removing tab while changing the collection

If your enumerable object has implemented the INotifyPropertyChanged interface, PersistTabItemsSourceHandler will listen to the CollectionChanged event. It will keep the enumerable object in sync with the tab items in the TabControl.

In the demo, you can see how this works by clicking the Add Page and Remove Page buttons on top of the window.

Unfortunately, you can't do this in PersistTabBehavior because you are not binding your ViewModel to the tab. You are now adding a real TabItem to the tab.

One of the solutions is to override the default template of the TabItem, and you can do the binding there. In this demo, I am using the default template from MSDN. In the ContentPresenter, I have assigned the Content to the Header property.

There is quite a lot of style code you need to copy, but this is the best way I can think of.

Disposing the object

In order for the TabControl to be garbage collected after it is released from the UI, we have to make sure we clear all the references. Unlike Windows Forms controls, WPF controls have no Disposed event (because there is nothing to dispose). What we have to do is to listen for the Unloaded event. When the control is gone from the UI, this event will be triggered. At this point, we can dump our PersistTabItemsSourceHandler object.

Part 2 - PersistTabSelectedItemHandler

After I had completed PersistTabItemsSourceHandler, I thought I was done. However, I had missed one important point - the selected tab. Since we are adding a real TabItem to the TabControl, it won't work if you just bind your ViewModel to the SelectedItem property. The SelectedItem property will just give you the selected TabItem, but not the ViewModel sitting in the TabItem's DataContext.

In the demo, I have a SelectedPage property in the TabCotnrolViewModel. This is used to keep track of which tab is currently active. This item is also bound to the top left of the window. You can see the text changing when switching tabs.

TwoWay Binding

PersistTabSelectedItemHandler and PersistTabItemsSourceHandler are very similar. The main difference is that PersistTabSelectedItemHandler supports TwoWay binding. Which means that when the user selects a tab, the SelectedPage property will be updated. The other way around, when the SelectedTab property changes, the TabControl will activate the corresponding tab.