This blog post is re-posted from www.eriklieben.com.
Looking at the file tab of Word 2010, I’ve wondered if a WPF tab control could be transformed to this layout. At first, this felt like something that could never be done, without doing a lot of magic or building it from scratch. After playing around for a while, I discovered the power of WPF and its enormous flexibility.
The hardest part to figure out was creating the arrow on the right side of the selected item and keeping the light gray border line across the whole left bar. I’ve almost solved this (there is still a small 1px difference in my result), but it’s close enough.
The style I ended up with looks a little bit different than the actual control, but if you need to choose between rewriting a whole control or just only modifying its style, then I think accepting difference is more effective than going for the exact clone. And still, if you would want to use this, you would probably still transform it to your taste. You might need to optimize it for your usage, if you want to use it, because I’ve only tested it with the content below. It’s basically for you to learn how to do this, not to just copy & paste and reuse without understanding anything about it
Let’s start, first of all we will need to get the tab control in place:
<Window x:Class="TestClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl Name="tabSteps">
<TabItem Header="Info" IsSelected="True">
<TextBlock>Info content</TextBlock>
</TabItem>
<TabItem Header="Recent">
<TextBlock>Recent content tab</TextBlock>
</TabItem>
<TabItem Header="New">
<TextBlock>New content tab</TextBlock>
</TabItem>
<TabItem Header="Print">
<TextBlock>Print content tab</TextBlock>
</TabItem>
<TabItem Header="Save & Send">
<TextBlock>Save & send content tab</TextBlock>
</TabItem>
<TabItem Header="Help">
<TextBlock>Help tab</TextBlock>
</TabItem>
</TabControl>
</Window>
This will create a window, with a tab control like you would expect:
What we only want to do is modify the visual appearance of this tab control. To do this, we can make use of control template functionality of WPF. This functionality allows you to modify the rendering of all controls that make use of control templates.
We want to be able to use this control template anywhere, so we need to add a resource dictionary to our project that will contain our control template to reuse it.
Perform the following to add the Dictionary
:
- left mouse click on your project
- click add
- pick ‘Resource Dictionary’
Now in this resource dictionary, we will add the following XAML code:
<ControlTemplate x:Key="OfficeTabControl" TargetType="{x:Type TabControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="#FFE9ECEF"
Grid.Column="0"
BorderBrush="LightGray"
BorderThickness="1"
SnapsToDevicePixels="True" />
<StackPanel IsItemsHost="True"
Grid.Column="0"
Margin="0,0,-1,0"
SnapsToDevicePixels="True" />
<ContentPresenter
Content="{TemplateBinding SelectedContent}"
Grid.Column="1"
Margin="15,0,0,0" />
</Grid>
</ControlTemplate>
This will create a control template with the name OfficeTabControl with a grid inside. This grid is divided in 2 columns. We want one for the tabs and one for the actual content inside the tab. The left side of the grid contains a border containing just a gray background and a light gray border line. Above(after in code) this in the same column is a StackPanel containing a property IsItemsHost set to true
. This property will indicate that generated items (the tabs) will be placed in this panel. In the second column is a ContentPresenter. This is a placeholder for the content that is defined inside the tab.
Now let’s let our TabControl use our new style, set the Template property to the control template:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="OfficeTab.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<TabControl Name="tabSteps" Template="{StaticResource OfficeTabControl}">
To do this, you will need to add the resource dictionary to your window resources. Then set the template to use our control template we’ve just created. When you run the application now, you already see a bit of the structure we want.
Now we need to modify the rendering of the Tab control item, this is the tab on the page, which is now on the left side. The rendering of this control can be modified by changing the control template the control. Above the Grid of the control template in the resource dictionary, we will add the following XAML:
<ControlTemplate x:Key="OfficeTabControl" TargetType="{x:Type TabControl}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="True">
<ContentPresenter
Name="buttonText"
Margin="15,0,5,0"
TextBlock.FontFamily="Calibri"
TextBlock.FontSize="12pt"
TextBlock.Foreground="Black"
Content="{TemplateBinding Header}"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ControlTemplate.Resources>
When we run the application now, we see that we removed the button look from the tabs:
Now let’s start adding more style to it. To do this, we will make use of visual states.
Visual states can be seen as a method to improve the overlap between the developer (that works in code) and the designer, which designs the layout for a given state. The developer can share certain states, for example mouse over and the designer can create a look for that.
The visual state templates can be added within the Grid. We can set the Normal state and the MouseOver state, to add the mouse over effect. To do this, we need to create a border that contains the background and the visual look of that state.
We add this style by creating 2 borders, one for the gray line on the right side and one for the background color and the light blue top and bottom borders.
<Border Name="hoverShape"
Height="40"
Margin="0,0,1,0"
SnapsToDevicePixels="True"
BorderThickness="0,0,1,0"
BorderBrush="LightGray">
<Border BorderBrush="#FFA1B7EA"
BorderThickness="0,1"
Background="#FFE5EEF9"
Height="40"
SnapsToDevicePixels="True" />
</Border>
After that, we will create the storyboards for the visual states. One for the normal state, which will animate the hoverShape opacity to zero. And one for the mouse over state, which will animate the hoverShape opacity to 1
.
<Grid SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="hoverShape"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:.1"/>
</Storyboard>
</VisualState>
<VisualState Name="Normal">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="hoverShape"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:.1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
If we start the application now, it will look like below:
Time to add the blue selected item bar. Below the hoverShape, we will add another border called buttonShape, this allows us to add a 2 pixel darker blue border on the top and bottom of the blue shape.
(You can skip this step.) Create a screenshot of the shape you want and open Expression Blend, with the pen tool, create a path that follows the shape you want, see the black line below. Go to, view, xaml, and there you will see your path, the data property contains the values that represent the shape.
You can for now just copy the code sample below, this also contains the correct gradient and properties, but I’ve just added the above to show how you can do it yourself.
Your XAML code should now look like below:
<Border
Name="buttonShape"
Opacity="0"
BorderBrush="#FF0343A6"
BorderThickness="0,2"
Height="40"
SnapsToDevicePixels="True">
<Path
Data="M214,108 L346,108 346.125,118.125 337.75,
126.125 346.375,134 346.375,143.875 214.25,144.25 z"
SnapsToDevicePixels="True"
Stretch="Fill"
Height="40">
<Path.Fill>
<RadialGradientBrush GradientOrigin="0.2,0.5"
RadiusX="0.8" RadiusY="0.8">
<GradientStop Color="#FF5FA3F6" Offset="0" />
<GradientStop Color="#FF0C55B9" Offset="1" />
</RadialGradientBrush>
</Path.Fill>
</Path>
</Border>
When you run the application now, nothing is different yet. That’s because we still need to define the visual states for the selected tab.
When adding the code below, you will add the visual state group SelectionStates
and add the behavior/storyboards for the selected and unselected state. As you can see, we are again modifying the opacity of shapes to create the effects. To get the text color white, I’ve added the color animation. For some reason, this gives an error in the designer, because it is unable to identify the TextBlock
, I don’t know why this is yet. I am not a WPF expert, but maybe someone out there knows why this is or how to solve it
<VisualStateGroup Name="SelectionStates">
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="buttonShape"
Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:.3"/>
<DoubleAnimation
Storyboard.TargetName="hoverShape"
Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:.1"/>
<ColorAnimation
Storyboard.TargetName="buttonText"
Storyboard.TargetProperty=
"(TextBlock.Foreground).(SolidColorBrush.Color)"
To="White" Duration="0:0:.1" />
</Storyboard>
</VisualState>
<VisualState Name="Unselected">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="buttonShape"
Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:.1"/>
<DoubleAnimation
Storyboard.TargetName="hoverShape"
Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:.1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
When we are running the application now, we see it gets closer to the end result:
Still there is one problem, we need to hide the background that is still visible below the arrow. We can do this by adding a white shape below it and add the opacity change to the storyboards for the selected and unselected states.
<VisualStateGroup Name="SelectionStates">
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="buttonBackgroundShape"
Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState Name="Unselected">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="buttonBackgroundShape"
Storyboard.TargetProperty="Opacity" To="0" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
When we run the application now, we can see the end result:
Have fun. The source for the style can be found here (8.37 KB).
CodeProject