Introduction
Arguable Office 2007 has revolutionized the way modern user interfaces will look like with great innovations like the ribbon... The control I would like to focus on in this article though is the Office 2007 Navigation Pane, or more specifically, how to recreate the Navigation Pane by restyling a TabControl
!
The WPF team has done an excellent job of reducing the need to create custom controls by allowing "lookless controls" to be re-styled! This is how the Navigation Pane looks:
And this is how our standard TabControl
currently looks:
Here is the XAML:
<TabControl VerticalAlignment="Stretch" Width="360" Height="Auto"
TabStripPlacement="Bottom">
<TabItem Header="Mail">
<Grid/>
</TabItem>
<TabItem Header="Calendar">
<Grid/>
</TabItem>
<TabItem Header="Contacts">
<Grid/>
</TabItem>
<TabItem Header="Tasks">
<Grid/>
</TabItem>
</TabControl>
Before I begin restyling, to keep the beginner status for my article, I will briefly explain what a Style
and Template
are and how they are used here!
What is a Style?
A Style
's main function is to group together property values that could otherwise be set individually. The intent is to then share this group of values among multiple elements. If you look at a simple Style
in XAML, you will notice that generally it has a TargetType
set and then a collection of Setter
s. Each Setter
then allows you to replace a given Property
value with the provided value. Here is a very basic example:
<Style x:Key="SimpleStyle" TargetType="{x:Type TabControl}">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="Yellow" />
</Style>
While this is the standard definition of a Style
, in this restyle article, it is more used to just replace the Template
property.
What is a Template?
A Template
allows you to completely replace an element’s visual tree with anything you can dream up, while keeping all of its functionality intact. Here is an example of how we use a style
/setter
to replace a Template
.
<Style x:Key="SimpleStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Since the Template
is just a property on the Control
object (from which TabControl
derives), it can also be replaced by using a setter
! We can now replace the complete visual tree while keeping the basic functionality of a TabControl
!
For more information about deriving from Control
, have a look here.
Restyling the TabControl
The TabControl
uses a TabPanel
to layout the TabControl
's tabs (or buttons). We will now replace this with a StackPanel
:
<Style x:Key="OutlookTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="BorderBrush" Value=
"{x:Static Microsoft_Windows_Themes:ClassicBorderDecorator.ClassicBorderBrush}"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="MinWidth" Value="10"/>
<Setter Property="MinHeight" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid ClipToBounds="true" SnapsToDevicePixels="true"
KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid x:Name="ContentPanel" Grid.Column="0" Grid.Row="1"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<Microsoft_Windows_Themes:ClassicBorderDecorator
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderStyle="Raised"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter SnapsToDevicePixels=
"{TemplateBinding SnapsToDevicePixels}" Margin="2,2,2,2"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"/>
</Microsoft_Windows_Themes:ClassicBorderDecorator>
</Grid>
<StackPanel HorizontalAlignment="Stretch" Margin="0,-2,0,0"
x:Name="HeaderPanel" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Grid.Row="1" IsItemsHost="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Bottom">
<Setter Property="Grid.Row"
TargetName="ContentPanel" Value="0"/>
<Setter Property="Height"
TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height"
TargetName="RowDefinition1" Value="Auto"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Left">
<Setter Property="Grid.Row"
TargetName="ContentPanel" Value="0"/>
<Setter Property="Grid.Column"
TargetName="ContentPanel" Value="1"/>
<Setter Property="Width"
TargetName="ColumnDefinition0" Value="Auto"/>
<Setter Property="Width"
TargetName="ColumnDefinition1" Value="*"/>
<Setter Property="Height"
TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height"
TargetName="RowDefinition1" Value="0"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Right">
<Setter Property="Grid.Row"
TargetName="ContentPanel" Value="0"/>
<Setter Property="Grid.Column"
TargetName="ContentPanel" Value="0"/>
<Setter Property="Width"
TargetName="ColumnDefinition0" Value="*"/>
<Setter Property="Width"
TargetName="ColumnDefinition1" Value="Auto"/>
<Setter Property="Height"
TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height"
TargetName="RowDefinition1" Value="0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource
{x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
All of this is automatically created by blend by right-clicking on the TabControl
and selecting "Edit Control Parts (Template
)" -> "Edit a Copy..."
Next, in XAML just replace the TabPanel
with the StackPanel
. The only important property to set on the StackPanel
is the IsItemsHost="true"
.
After replacing the TabPanel
with a StackPanel
, set each TabItems
height to 30
, the TabControl
's Background
to White
and the BorderBrush
to #FF6593CF
.
Well, this looks better...
What are Resources?
Resources or more specifically, ResourceDictionary
is just a dictionary of key/value pairs that can be accessed from XAML. It is used here to define brushes that are commonly used. Here is a simple example:
<SolidColorBrush x:Key="OutlookButtonForeground" Color="#FF204D89"/>
and here a use for it:
Foreground="{DynamicResource OutlookButtonForeground}"
Restyling the TabItem
Next, create a style for a TabItem
by right-clicking on one of them and selecting "Edit Control Parts (Template
)" -> "Edit a Copy..."
Before I dig into the default style created, I just want to create two brush resources. The first brush resource is the normal color of a Navigation Pane button:
<LinearGradientBrush x:Key="OutlookButtonBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD9EDFF" Offset="0"/>
<GradientStop Color="#FFC0DEFF" Offset="0.445"/>
<GradientStop Color="#FFC0D9GB" Offset="1"/>
<GradientStop Color="#FFAFD1F8" Offset="0.53"/>
</LinearGradientBrush>
And the next is the default text color of a button:
<SolidColorBrush x:Key="OutlookButtonForeground" Color="#FF204D89"/>
So, each TabItem
should now look something like this:
<TabItem Header="Tasks" Height="30" Style="{DynamicResource OutlookTabItemStyle}"
Background="{DynamicResource OutlookButtonBackground}"
Foreground="{an>DynamicResource OutlookButtonForeground}">
<Grid/>
</TabItem>
Creating a style
for the TabItem
has two purposes: Align the TabItem
's header to the left and change the TabItem
's border from a ClassicBorder
to a normal Border (for more control).
<Style x:Key="OutlookTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
<Setter Property="Padding" Value="12,2,12,2"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd"
Background="{TemplateBinding Background}"
BorderThickness="1" BorderBrush="#FF6593CF">
<ContentPresenter SnapsToDevicePixels=
"{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{Binding Path=VerticalContentAlignment,
RelativeSource={RelativeSource AncestorType=
{x:Type ItemsControl}}}"
ContentSource="Header" RecognizesAccessKey="True"
HorizontalAlignment="Left"/>
</Border>
<ControlTemplate.Triggers>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is how it looks now:
Ahhh... even better! So, what's next?
What are Triggers?
Triggers
have a collection of Setter
s just like Style
(and/or collections of TriggerActions
). But whereas a Style
applies its values unconditionally, a trigger
performs its work based on one or more conditions. There are three types of triggers
:
Property triggers
— Invoked when the value of a dependency property changes
Data triggers
— Invoked when the value of a plain .NET property changes
Event triggers
— Invoked when a routed event is raised
We will make use of an Event Trigger
. The TabItem
provides us with an IsSelected
routed event!
Selection
We now need to indicate (by changing color) that an item is selected. Again, we create a brush to represent the color the button needs to change to once highlighted:
<LinearGradientBrush x:Key="OutlookButtonHighlight" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFBD69" Offset="0"/>
<GradientStop Color="#FFFFB75A" Offset="0.0967"/>
<GradientStop Color="#FFFFB14C" Offset="0.2580"/>
<GradientStop Color="#FFFB8C3C" Offset="0.3870"/>
<GradientStop Color="#FFFEB461" Offset="0.9677"/>
<GradientStop Color="#FFFEBB67" Offset="1"/>
</LinearGradientBrush>
And here is the trigger
:
<ControlTemplate.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Background" TargetName="Bd"
Value="{DynamicResource OutlookButtonHighlight}"/>
</Trigger>
</ControlTemplate.Triggers>
Here is how it looks:
That's better...
That is basically all that is required to restyle a TabControl
!
History
- 4th March, 2008 - Initial release
- 5th March, 2008 - Changed the title, updated the colors
- 23rd June, 2008 - Added source file
Please vote for this article if you liked it and also visit my blog.
Thank you!