I was making this WPF based application on weekends and wanted it to have a gel like look. I did not want to create a new control instead I wanted to make sure that I used WPF styles to change the look of the tab. I decided to share this experience so that anyone who wants to do this does not have to do any research. The things that I will do to make this happen required very simple and basic WPF knowledge and is thus considered as kid stuff to many WPF pros. But none the less, I am interested to share this. Here goes ...
Download GelTab.zip
Styling and Templates
WPF has the capability of applying styles like css does to html. Any WPF control that we use has something called a control template. The control template defines how the control will look. The control template may contain triggers also. Let try to understand from the diagram below how WPF renders a control UI.
The basic WPF control has a template which it uses to render the UI from the control definition. This can be changed by applying a custom style. In order to do that we must first define a custom style where we can modify the template for the control. By doing so we will able to completely change the look and behavior of the control. We can even make a tab look like a listbox a tree. The possibilities are endless. However today we will only try to make the tab look like blue gel based (Aqua) tab. Which should look like this (by the way. I have not yet decided on the blue shade ... it still looks a little ugly) ...
In order change a tab to look like this I will need to write a style and modify that style.
The control code will stay the same but the style template will change. In order to this we will create a resource dictionary. So we have created a resource dictionary called GelTabStyle.xaml. In order for this to be used by the application the this will need to be added into the application dictionary ( we can also put it in the file with the tab instance, but that is not a good practice). When we add such a resource dictionary from the Visual studio or Microsoft Expression Blend the IDE automatically modifies the App.xaml to include the resource dictionary. Here is how our App.xaml should look like ...
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GelTabStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
WPF Control Composition & Templates
As I have already stated that a WPF control has templates to define its UI, what are these templates made of? They are made of WPF shapes, other WPF controls, triggers and GDI resource definitions. To have a very simple idea see the diagram below...
A control has a template which is made of WPF basic elements and controls, and each of those controls have templates which have WPF elements and controls in them. We can define a style in a resource dictionary (in our case the GelTabStyle.xaml) and inside a style we can define our custom control template. Before we go any further lets try to look up a tab controls template.
Anatomy of a Tab
If you pry open the template that renders the tab control with Expresssion Blend then you would find that there is a grid with 2 rows. The first row contains the tab item headers and second part contains the tab body. See the diagram below ...
Since we want the tab headers to be gradient gel like we will define a style for those and will call them "GelTabItem" and also will need to change the bottom container and will call that "GelTab" style. For our case we only the the tab headers to placed at top ... so we wont bother with placing them at left, right or bottom.
Inside the resource dictionary we have defined some colors and some gradient brushes to do our painting, which looks like this ...
<Color x:Key="TabItemGradStart" R="#AA" G="#AA" B="#BB"/>
<Color x:Key="TabItemGradEnd" R="#EE" G="#EE" B="#FF" A="#FF"/>
...
...
<LinearGradientBrush x:Key="TabBackgorundBrush" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Offset="0" Color="{StaticResource TabItemGradStart}"/>
<GradientStop Offset="1" Color="{StaticResource TabItemGradEnd}"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="TabBackgorundBrushSel" StartPoint="0,0" EndPoint="0,1" >
...
...
So we have defined the basic drawing resources, lets look at the style header
<Style x:Key="GelTabItem" TargetType="{x:Type TabItem}">
If you look carefully that you would find that this style can only apply to a TabItem object and also it will only apply to those tab items which have their style set to "GelITabtem". By the way if we wanted to make sure all the tab items in the application would use this style (except the ones that has explicit style defined) then we would remove the x:Key keyword from the style. That would ensure that all tab items by default will have the style applied. However that is not our goal.
TabItems
Lets see how the xaml code makes the header part with tab items to be styled. The diagram below will make it clear.
As you can see that we have used a border with rounded corners to make a border for the gel tab item, we have also used a rectangle to fill the gradient area of the button. We used the gradient brush that we defined at the start of the resource file.
Triggers
In order make sure that the selected tab has one color and the unselected tabs have different color we used triggers to change the back color. See the code snippet below
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="TabItemBackgorund" Property="Fill"
Value="{StaticResource TabBackgorundBrushSel}" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="TabItemBackgorund" Property="Fill"
Value="{StaticResource TabBackgorundBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="TabItemBackgorund" Property="Fill"
Value="{StaticResource TabBackgorundBrushDisabled}" />
</Trigger>
</ControlTemplate.Triggers>
When the tab is selected use a brush called "TabBackgorundBrushsel" as the fill value of the rectangle and when it is not selected we use the brush called "TabBackgorundBrush" to have an effect of unselected item. Similarly we use "TabBackgorundBrushDisabled" to fill the rectangle for the disabled tab item. See figure ...
Tab Body
If you observe the tab body style then you would see a control with the "controlpresenter" tag which actually contains the controls that we place inside the tab.
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="Transparent" BorderThickness="0" Grid.Row="1"
CornerRadius="5,5,5,5" Background="Transparent" Padding="5,5,5,5" >
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5,5,5,5"
Background="{StaticResource TabBackgorundBrushSel}" >
<ContentPresenter ContentSource="SelectedContent" />
</Border>
</Border>
<TabPanel Grid.Row="0" IsItemsHost="true"/>
</Grid>
</ControlTemplate>
If we observe the xaml above we would see that the body resides in second row and the header panel (<TabPanel> in xaml) resides at top row this is how the bottom and top placement of the two different parts are defined. We have made the body rounded and also padded it and used the selected brush to paint it.
Using the styles with the tabs
There are two ways to apply the style. Depending on how we want the tabs to look it may change. One strategy is to use css class like style that we made and apply them the control items. Another is to make the apply the style whole application wide. In order to make it application wide we would have to remove the x:key that names the style.
Now if we want to make it specific for a control we use the style tag to specify the style like the code below ( the part marked in bold letters)
<TabControl Style="{DynamicResource GelTabControl}" >
<TabItem Header="Rapid Note" Style="{DynamicResource GelTabItem}">