Click here to Skip to main content
Click here to Skip to main content

Creating an Outlook Navigation Pane by Restyling a WPF TabControl

By , 24 Jun 2008
 

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:

OutlookPanel.jpg

And this is how our standard TabControl currently looks:

TabControl.jpg

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 Setters. 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}">
                <!-- The new visual tree should be placed here -->
            </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..."

BlendTemplateEditCopy.jpg

Next, in XAML just replace the TabPanel with the StackPanel. The only important property to set on the StackPanel is the IsItemsHost="true".

HeaderControl.jpg

After replacing the TabPanel with a StackPanel, set each TabItems height to 30, the TabControl's Background to White and the BorderBrush to #FF6593CF.

AfterApplyingBasicStyle.jpg

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: "BetterStyle.jpg"

BetterStyle.jpg

Ahhh... even better! So, what's next?

What are Triggers?

Triggers have a collection of Setters 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:

EvenBeter.jpg

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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

rudigrobler
South Africa South Africa
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSilverlightmemberspook30 Sep '09 - 16:38 
Great Article! Really well though-out use of the principles behind WPF. I'm curious though...will this work in Silverlight?   -k
GeneralRe: Silverlightmemberrudigrobler30 May '10 - 2:02 
Silverlight version now available: http://www.rudigrobler.net/Blog/outlookbar-for-wpf--silverlight[^]
GeneralRe: SilverlightmemberJiltedCitizen15 Jul '10 - 7:45 
Is there a theming part coming? I'd like to use your control but my xaml abilities are a bit poor. Not sure how I'd marry the styling from this article with the control you made.
GeneralTemplate problemmemberMrPMorris9 Jul '09 - 4:10 
I've applied your nice code to a window.   <TabItem Header="Meh" Height="30" Style="{DynamicResource OutlookTabItemStyle}" Background="{DynamicResource OutlookButtonBackground}" Foreground="{DynamicResource OutlookButtonForeground}"> <Grid/> </TabItem>   That...
Generalgoodgroupzhujinlong1984091316 Apr '09 - 20:45 
good
QuestionHow can i add picture and events..memberfifiza10 Mar '09 - 2:46 
Hi, Thank you so much for your code it's a very good job. I wonder if you could show me how to add events click to each element of the tab in order to display a tree view or other sub menu.   Thanks.
Questionhow can i provide shrink functonality in this controlmemberOm S Pathak15 Dec '08 - 4:02 
Thanks for the nice post. but i would like to know how can i provide shrink functionality to like we can do in the Outlook 2007. when we shrink it to down the respective icons shown to the bootom panel.   Please tell me how can i do this into my application.
GeneralNice Job...memberCastle Rider16 Nov '08 - 18:16 
Hi there.   This a awesome piece of work. Thanks for sharing.   The one thing missing is the OverflowPanel for the NavigationPane. Do you have any idea how to do it? I tried by myself but unable to find a solution.   Castle Rider What if I freeze??? Don't forget...
QuestionAbout the Images/Iconsmemberdearcyrix12 Oct '08 - 18:20 
I learned a lot from your passage.   I have got a question, how to Insert the "envelope icon" or other kind of controls into every tabitem ? also i have no idea how to make the header content to the left ?   Please give some instructions Thank you very much
QuestionTabStripPlacement - does it work?memberIndy200526 Jun '08 - 18:07 
Hello Rudi,   Excellent article, my 5!   I opened your source code, edited the XAML to set TabStripPlacement to "Left" - firing up the app does not position the tabs to the left, in fact, they are not visible at all! Same for "Right". Unless I am doing something absolutely wrong...
AnswerRe: TabStripPlacement - does it work?memberrudigrobler26 Jun '08 - 20:10 
thank you...   Just have a look at the Triggers section... I never even tried to hook them up.... search for TabStripPlacement and change the triggers!   Their is basically a trigger that changes the layout based on the current selected TabStripPlacement   Regards,  ...
GeneralRe: TabStripPlacement - does it work?memberholy_spirit18 Jan '09 - 23:43 
I have the same issue, Can you explain more please And thanks for the helpful article
AnswerRe: TabStripPlacement - does it work?memberholy_spirit19 Jan '09 - 1:19 
Don't bother, I knew how to do that just change Grid.Row to 0 and VerticalAlignment to top   StackPanel HorizontalAlignment="Stretch" Margin="0,-2,0,0" x:Name="HeaderPanel" VerticalAlignment="Top" Width="Auto" Height="Auto" Grid.Row="0"...
QuestionPlease help mememberMember 268137218 Jun '08 - 22:32 
Thanks for the useful codes you have. I also need help in the following commond error: Microsoft_Windows_Themes:ClassicBorderDecorator thanks
AnswerRe: Please help mememberrudigrobler22 Jun '08 - 23:51 
Hi,   I send the complete code to codeproject to update my article. this shoudl be made available soon!   This error can be resolved by importing the correct namespace  ...
GeneralGood onememberpgr200616 May '08 - 2:16 
Very interesting
GeneralRe: Good onememberrudigrobler22 Jun '08 - 23:53 
Tnx
GeneralNice Job!member BOTs - running in my mind 26 Mar '08 - 19:52 
This is a pretty cool work you did.   It would be great, if you could upload the source code.   I tried with the code snippets above, but hard to fix it.   [Venkatesh Mookkan] My: Website | Yahoo Group | Blog Spot
GeneralRe: Nice Job!memberrudigrobler22 Jun '08 - 23:54 
Tnx,   I send the source to CodeProject and thy should update the article soon!   Regards,   Rudi Grobler http://dotnet.org.za/rudi
GeneralNeatomvpSacha Barber6 Mar '08 - 8:59 
Cool. Good work   Sacha Barber Microsoft Visual C# MVP 2008Codeproject MVP 2008Your best friend is you. I'm my best friend too. We share the same view, and never argue   My Blog : sachabarber.net
GeneralRe: Neatomemberrudigrobler6 Mar '08 - 19:42 
Tnx Sacha!
GeneralOutlook brushesmemberBenoît Dion6 Mar '08 - 0:21 
I had to do the same kind of Outlook like UI.   Here are the brushes:         <lineargradientbrush x:key="NavigationPaneButtonBackground" endpoint="0.5,1" startpoint="0.5,0">            <gradientstop color="#FFC0DBFF"...
GeneralNicememberYogesh Jagota5 Mar '08 - 18:35 
A much advanced example from Microsoft allows to make a exact replica of a Outlook UI. Although I cannot find the example on the msdn site anymore, but I have the source code. The example was called Outlook UI HOL.   This link lead me to the example, but does not work anymore, although...
GeneralRe: NicememberAngelo Cresta5 Mar '08 - 23:35 
The correct link is:   http://blogs.msdn.com/tims/archive/2007/06/13/wpf-hands-on-lab-build-an-outlook-2007-ui-clone.aspx[^]   Regards /// Angel
GeneralBlend or VS2008memberizwaldschwartz5 Mar '08 - 3:10 
This looks good overall. I'd be interested in the entirety of the source as well.   Was this created completely via Blend? Or was VS '08 used at all?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 24 Jun 2008
Article Copyright 2008 by rudigrobler
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid