Introduction
In this tip, I want to show you a simple way to style the borders of a TabControl
. This method allows the border to be any thickness. I will show you how to create a ControlTemplate
that will make this happen and a little trick to get the borders of selected/unselected tabs to look right. Below, you can see a stock TabControl
and a TabControl
with thicker and rounded borders.
Overview of TabControl
First, let’s go over the TabControl
. A TabControl
consists of two main parts. A TabPanel
that serves as the header and a ContentPresenter
for the SelectedContent
which holds the selected view. The TabPanel
has a collection of TabItems
that each hold another ContentPresenter
for the Header content of each tab. Since the tabs are contained in a separate TabPanel
, a little trick needs to be added to make the selected tab look right. Below, you can see all the parts.
TabControl Implementation
We create a control template for the TabControl
. We will use a Grid
to position the ContentPresenter
and the TabPanel
in the TabControl
. The Grid needs 3 rows. You also want to set the Grid
property UseLayoutRounding
to True
. This way the TabPanel
tabs and the ContentPresetner
border will line up.
<ControlTemplate TargetType="TabControl">
<Grid UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition Height="31" />
<RowDefinition Height="4" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid>
...
</ControlTemplate>
The top row will hold the TabPanel
. The second row will be an overlap area for the TabPanel
and the ContentPresenter
. This will allow for tabs to have a solid border on the bottom matching the border around the ContentPresenter
when they are not selected and allow for selected tabs to have no border on the bottom and look like the tab is part of the ContentPresenter
. The trick is to set the TabPanel
properties Grid.Row=0
, RowSpan=2
and Panel.ZIndex=1
.
<TabPanel IsItemsHost="True"
Grid.Row="0"
Grid.RowSpan="2"
Panel.ZIndex="1" />
Then set the Border
around the ContentPresenter
properties Grid.Row=1
, RowSpan=2
and Panel.ZIndex=0
. Here, we also set the corners to be rounded.
<Border Grid.Row="1"
Grid.RowSpan="2"
CornerRadius="{StaticResource TabConrol_Corner_Radius}"
Panel.ZIndex="0"
BorderThickness="{StaticResource Tab_Border_Thickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter Margin="4"
ContentSource="SelectedContent" />
</Border>
The middle row needs to be the same width as the border and the top row needs to be the selected tab height minus the border width. In our case, selected tab height is 35, border is 4, so the top row height is 31(35-4) and the middle row height is 4. What this does is it puts the TabPanel
with all the tabs on top of the top border. This will have the effect of showing the bottom border outside tabs and hiding it behind tabs.
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition Height="31" />
<RowDefinition Height="4" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabPanel IsItemsHost="True"
Grid.Row="0"
Grid.RowSpan="2"
Panel.ZIndex="1" />
<Border Grid.Row="1"
Grid.RowSpan="2"
CornerRadius="{StaticResource TabConrol_Corner_Radius}"
Panel.ZIndex="0"
BorderThickness="{StaticResource Tab_Border_Thickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter Margin="4"
ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Each TabItem
also needs a ControlTemplate
that wraps the ContentPresenter
in a border. The border is the same thickness as the border around the main ContentPresenter
.
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="tabBorder"
Height="30"
Width="50"
BorderBrush="{StaticResource Tab_Border_Color}"
BorderThickness="{StaticResource Tab_Border_Thickness}"
VerticalAlignment="Bottom"
Margin="{StaticResource TabItem_Spacer_Between_Tabs}"
CornerRadius="{StaticResource TabItem_Corner_Radius}"
Background="{StaticResource Tab_Background_Color}">
<ContentPresenter x:Name="tabContent"
ContentSource="Header"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Border>
</ControlTemplate>
There is a style trigger when the tab is selected, IsSelected=True
, which sets properties to make the tab look selected. The border around the tab here is swapped from 4,4,4,4 to 4,4,4,0.
<Window.Resources>
<Thickness x:Key="Tab_Border_Thickness">4,4,4,4</Thickness>
<Thickness x:Key="TabItem_Border_Thickness_Selected">4,4,4,0</Thickness>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter TargetName="tabBorder"
Property="BorderThickness"
Value="{StaticResource TabItem_Border_Thickness_Selected}" />
<Setter TargetName="tabBorder"
Property="Height"
Value="35" />
<Setter TargetName="tabBorder"
Property="BorderBrush"
Value="{StaticResource TabItem_Border_Color_Selected}" />
<Setter TargetName="tabBorder"
Property="Background"
Value="{StaticResource TabItem_Background_Color_Selected}" />
</Trigger>
</ControlTemplate.Triggers>
Notice the bottom no longer has a border which allows the tab background to flow into the main ContentPresenter
for a selected tab. The rest of the unselected tabs have a bottom border which look like they are part of the border around the main ContentPresenter
. The trigger also increases the height of the selected tab and also adds some highlighting. This is the trick to get the border to look right for selected and unselected tabs.
Conclusion
Hopefully, this little trick will help you in styling a TabControl
. Please leave any comments or suggestions. You can also get the source at https://github.com/mw12345/TabControlDemo.