The TabControl class derives from Selector, which derives from ItemsControl. It’s supposed to host a collection of TabItem objects, with easy switching ability by the user, effectively creating the so-called “Tabbed Document Interface” that is so common today, replacing the old Multiple Document Interface (MDI) scheme.
At first, it looks like TabControl is just what is needed for such an interface. For example, let’s create a simple notepad close with multiple open documents managed by a TabControl:
<TabControl>
<TabItem Header="File 1">
<TextBox AcceptsReturn="True" Text="Text 1"/>
</TabItem>
<TabItem Header="File 2">
<TextBox AcceptsReturn="True" Text="Text 2"/>
</TabItem>
</TabControl>
This creates two tabs with two textboxes.
Although this simple code works, and by extension we can create additional TabItem objects as needed – this becomes clumsy as we need to add or remove tabs manually, perhaps create a more interesting header, etc. This is not the true WPF way. We need data templates and data binding.
Our first attempt will try to mimic the way we work with other ItemsControl types – by using the ItemTemplate property:
<TabControl Grid.Row="1" ItemsSource="{Binding Documents}"> <TabControl.ItemTemplate>
<DataTemplate>
<TextBox Text="Hello" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
The ItemsSource property is bound to some ObservableCollection<>, on which we call Add when a new document should be added. After calling Add twice, this is the disappointing result:
It seems that “Items” are in fact the tabs themselves – the textbox used for editing is part of the tab, and the “Content” has been fiilled with the ToString() of our custom Document type.
Looking again at TabControl, we find a ContentTemplate property that sounds promising. Let’s try that:
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="Hello" />
</DataTemplate>
</TabControl.ContentTemplate>
At first glance, it seems we got it just about right:
Until we realize, that there is in fact only one textbox instead of two! We can add as many tabs as we want, no new textboxes are actually created. On second thought, maybe this is to be expected, because ContentTemplate is for the TabControl, not the TabItems themselves. The documentation states that this property is applied if it does not exist for individual TabItems. Well, maybe we should apply it for TabItems.
So, we’ll try another approach. Let’s template the TabItems and not the TabControl. We can do that by using the ItemContainerStyle property. Perhaps we should create the DataTemplate there:
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="Hello" AcceptsReturn="True"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
Running this produces the same result as the previous code! That is, only one textbox is ever created! This is somewhat strange, as we are customizing the content of each and every TabItem, so the textbox creation should repeat.
After some investigation, including using Snoop to look at the actual created visuals, we find the tabs are created as needed, but the content is not.
This seems like a kind of bug to me, as we can create multiple textboxes (for example) using plain old Items.Add style code. We can’t get that with data binding and data templates. I’ve even tried to replace the control template of TabItems, but the result is pretty much the same.
How can we create “true” tab items without deriving new classes? One approach I’ve used is using plain old ListBoxes with some interesting customizations. For the details, stay tuned for part 2 in this two part (exciting) series.