Introduce DataBase,Asp.net,JavaScript,Xml,Html,Css,Sql,Php,ASP.NET Controls,AJAX,Tools,HTML,CSS,JavaScript,Open Source Project,WPF,.Net Framework,Linq
Top Recommended Hosting

WPF: Creating a Outlook Navigation Pane by restyling a WPF TabControl

by the3factory 3/5/2008 9:17:00 AM

The WPF team has done a 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 my biginner status for my article, I will brieflly explain what a style and template is and how it is used here!

What is a Style?

Styles 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 allow 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 a 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 derive), it can also be replaced by using a setter! We can now replace the complete visual tree while keeping the basic functionallity 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

Collapse
<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-clicing 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 is resources?

Resources or more specificially, 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 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="#CCE0EDFF" Offset="0"/>
<GradientStop Color="#FF6E9BD8" Offset="0.996"/>
<GradientStop Color="#66BDC8FF" Offset="0.391"/>
<GradientStop Color="#99BDCAFF" Offset="0.449"/>
<GradientStop Color="#B2A8BDF4" Offset="0.511"/>
</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 2 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 is next?

What is 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. Their are 3 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 a Event Trigger. The TabItem provides us with a IsSelected routed event!

Selection

We now need to indicate (by changing color) that a 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="#CCFFD6A4" Offset="0"/>
<GradientStop Color="#FFFED16A" Offset="1"/>
<GradientStop Color="#66BDC8FF" Offset="1"/>
<GradientStop Color="#99BDCAFF" Offset="1"/>
<GradientStop Color="#FFA8BDF4" 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

Selection.jpg

Ok, so my color is a little bit off... nothing a designer can't fix...

Tags:

WPF

Related posts

Sign up for PayPal and start accepting credit card payments instantly.


Powered by BlogEngine.NET 1.2.0.0