Wednesday, January 16, 2013

WPF TabControl Style

How to style a WPF TabControl?

The WPF TabControl is control used to group the contents in tabular way. This is very usable when you as a UI designer would like to save more space in your Window while configuring more fields in the UI.

Try to look a little bit the normal TabControl of the WPF.


You'll see the TabControl contains four headers that separate the contents based on the needs per group. In WPF, you call the items/headers as TabItem.

The TabItem object is the one who is being added as a child of the TabControl and to be the container of the other controls inside TabControl. You'll see in the above image the TabItem(s) labeled General, Actions, Options and Tools. So every control placed in General tab couldn't be seen in Actions tab as they were a separate group.
Let's go directly how could you customize or design your favorite TabControl.

Target TabControl and TabItem Design

There are no PARTS on the template of the TabControl, all you have to do is to customized it with the combinations of available controls from WPF. You should also create for your own Style for TabItem (same as TabControl it is a composite control and don't have any PART on its template).

Our target design for TabControl is below:


First you  should create a Style for TabItem as it is required when you are applying a template for TabControl. With the image above, it has three style existed for TabItem (styleTabItemLeft, styleTabItemDefault and styleTabItemRight). Each of them has different style and border.

Style for left TabItem

For the left style of the above's TabItem, we need to create a left rounded style and a little gradient background and soft text. Below is the XAML for this one.

<Style x:Key="styleTabItemLeft" TargetType="{x:Type TabItem}">
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border x:Name="rightBorder"
                        Background="{StaticResource ResourceKey=gradientBrushSegmentedTabItem}"
                        BorderThickness="0,0,1,0"
                        BorderBrush="#032A6B"
                        CornerRadius="4,0,0,4">
                    <Border x:Name="leftBorder"
                            BorderThickness="1,0,0,0"
                            BorderBrush="#2172B1"
                            CornerRadius="4,0,0,4">
                        <ContentPresenter x:Name="ContentSite"
                                            ContentSource="Header"
                                            Grid.Row="1"
                                            HorizontalAlignment="Stretch"
                                            Margin="20,4,20,5"
                                            RecognizesAccessKey="True"
                                            VerticalAlignment="Center"
                                            TextBlock.FontSize="14"
                                            TextBlock.Foreground="#FFFFFF">
                            <ContentPresenter.Effect>
                                <DropShadowEffect BlurRadius="0.0"
                                                  Color="#032A6B"
                                                  Direction="90"
                                                  Opacity="1"
                                                  ShadowDepth="1" />
                            </ContentPresenter.Effect>
                        </ContentPresenter>
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="rightBorder"
                                Property="Background"
                                Value="{StaticResource ResourceKey=gradientBrushSegmentedActiveTabItem}" />
                        <Setter TargetName="leftBorder"
                                Property="BorderThickness"
                                Value="0" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Style for right TabItem

The same with left TabItem, but only we adjusted the rounded corner to the right. The XAML below address it.

<Style x:Key="styleTabItemRight" TargetType="{x:Type TabItem}">
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border x:Name="rightBorder"
                        Background="{StaticResource ResourceKey=gradientBrushSegmentedTabItem}"
                        BorderThickness="0,0,1,0"
                        BorderBrush="#032A6B"
                        CornerRadius="0,4,4,0">
                    <Border x:Name="leftBorder"
                            BorderThickness="1,0,0,0"
                            BorderBrush="#2172B1"
                            CornerRadius="0,4,4,0">
                        <ContentPresenter x:Name="ContentSite"
                                            ContentSource="Header"
                                            Grid.Row="1"
                                            HorizontalAlignment="Stretch"
                                            Margin="20,4,20,5"
                                            RecognizesAccessKey="True"
                                            VerticalAlignment="Center"
                                            TextBlock.FontSize="14"
                                            TextBlock.Foreground="#FFFFFF">
                            <ContentPresenter.Effect>
                                <DropShadowEffect BlurRadius="0.0"
                                                    Color="#032A6B"
                                                    Direction="90"
                                                    Opacity="1"
                                                    ShadowDepth="1" />
                            </ContentPresenter.Effect>
                        </ContentPresenter>
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="rightBorder"
                                Property="Background"
                                Value="{StaticResource ResourceKey=gradientBrushSegmentedActiveTabItem}" />
                        <Setter TargetName="leftBorder"
                                Property="BorderThickness"
                                Value="0" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Style for default TabItem

This time this is not rounded and you'll notice an inset/outset if defaulted. The XAML below address it.

<Style x:Key="styleTabItemDefault" TargetType="{x:Type TabItem}">
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border x:Name="rightBorder"
                        Background="{StaticResource ResourceKey=gradientBrushSegmentedTabItem}"
                        BorderThickness="0,0,1,0"
                        BorderBrush="#032A6B">
                    <Border x:Name="leftBorder"
                            BorderThickness="1,0,0,0"
                            BorderBrush="#2172B1">
                        <ContentPresenter x:Name="ContentSite"
                                            ContentSource="Header"
                                            Grid.Row="1"
                                            HorizontalAlignment="Stretch"
                                            Margin="20,4,20,5"
                                            RecognizesAccessKey="True"
                                            VerticalAlignment="Center"
                                            TextBlock.FontSize="14"
                                            TextBlock.Foreground="#FFFFFF">
                            <ContentPresenter.Effect>
                                <DropShadowEffect BlurRadius="0.0"
                                                    Color="#032A6B"
                                                    Direction="90"
                                                    Opacity="1"
                                                    ShadowDepth="1" />
                            </ContentPresenter.Effect>
                        </ContentPresenter>
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="rightBorder"
                                Property="Background"
                                Value="{StaticResource ResourceKey=gradientBrushSegmentedActiveTabItem}" />
                        <Setter TargetName="leftBorder"
                                Property="BorderThickness"
                                Value="0" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

You will notice that the three styles almost the same in foundation but only the design for the rounded corners are different. You will also notice that some variables like gradientBrushSegmentedActiveTabItem, gradientBrushSegmentedTabItem were referenced. This is actually my resource object implemented to make the background of the TabItem header looks very nice. The XAML below is the code for this.

<LinearGradientBrush x:Key="gradientBrushTabControlHeader" StartPoint="0,0.5" EndPoint="1,0.5">
    <GradientStop Color="#002E8A" Offset="0" />
    <GradientStop Color="#0071B7" Offset="0.5" />
    <GradientStop Color="#002E8A" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="gradientBrushSegmentedTabItem" StartPoint="0.5,0" EndPoint="0.5,1">
    <GradientStop Color="#4C8BC0" Offset="0" />
    <GradientStop Color="#015CA3" Offset="0.49" />
    <GradientStop Color="#024795" Offset="0.50" />
    <GradientStop Color="#2C5198" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="gradientBrushSegmentedActiveTabItem" StartPoint="0.5,0" EndPoint="0.5,1">
    <GradientStop Color="#000098" Offset="0" />
    <GradientStop Color="#000075" Offset="0.33" />
    <GradientStop Color="#000075" Offset="0.66" />
    <GradientStop Color="#000082" Offset="1" />
</LinearGradientBrush>

Once you have already completed all the Style for the TabItem, next you should do your own Style for the TabControl applying the above styles you created.

Styling the TabControl

The WPF TabControl is a composite control and there is no PART on its template. This mean that you can do design anything you like to place on this template. But two thing you need not to  forget is placing down the ContentPresenter and StackPanel (setting the IsItemsHost = True) inside of its template. The ContentPresenter object will be the one be used by WPF to be the container of the content and the StackPanel will be the one to host the TabItem.

See the XAML below:

<Style x:Key="styleTabControl" TargetType="{x:Type TabControl}">
    <Setter Property="OverridesDefaultStyle" Value="True" />
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid KeyboardNavigation.TabNavigation="Local" ShowGridLines="False">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid Name="Header"
                            Background="{StaticResource ResourceKey=gradientBrushTabControlHeader}"
                            Grid.Column="0"
                            Grid.Row="0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                            <ColumnDefinition Width="Auto"></ColumnDefinition>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <Border Background="#032A6B"
                                BorderBrush="#032A6B"
                                BorderThickness="1,1,0,1"
                                CornerRadius="4"
                                Grid.Column="1"
                                Grid.Row="0"
                                KeyboardNavigation.TabIndex="1"
                                Margin="6"
                                Panel.ZIndex="1">
                            <StackPanel Name="HeaderPanel"
                                        IsItemsHost="True"
                                        Orientation="Horizontal">
                            </StackPanel>
                            <Border.BitmapEffect>
                                <DropShadowBitmapEffect Color="#0047CC"
                                                        Direction="180"
                                                        ShadowDepth="1"
                                                        Opacity="1"
                                                        Softness="1">
                                </DropShadowBitmapEffect>
                            </Border.BitmapEffect>
                        </Border>
                    </Grid>
                    <ContentPresenter Grid.Row="1" ContentSource="SelectedContent" Margin="5"
                                        KeyboardNavigation.DirectionalNavigation="Contained"
                                        KeyboardNavigation.TabIndex="2"
                                        KeyboardNavigation.TabNavigation="Local" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Above's XAML address the design of our target TabControl design. It creates a background for  its header and some drop shadow below it.

Applying the styles in the TabControl

All you have to do is to create a one ResourceDictionary file and place it inside your solution. Set the Build Action to Resource and add it as a resource of your Window (or the parent container of your TabControl).

Once you are done, apply each style in your control and its children (TabItems). Below XAML is the sample (placing TabControl inside the Window element).

<Window x:Class="CodesDirectory.WIN_TabControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF TabControl" Height="300" Width="300">
    <Window.Resources>
        <ResourceDictionary Source="/Styles/TabControl.xaml"></ResourceDictionary>
    </Window.Resources>
    <Grid>
        <TabControl Margin="5" Style="{DynamicResource ResourceKey=styleTabControl}">
            <TabItem Header="General" Style="{DynamicResource ResourceKey=styleTabItemLeft}">
                <TextBlock>The content for General tab.</TextBlock>
            </TabItem>
            <TabItem Header="Actions" Style="{DynamicResource ResourceKey=styleTabItemDefault}">
                <TextBlock>The content for Action tab.</TextBlock>
            </TabItem>
            <TabItem Header="Options" Style="{DynamicResource ResourceKey=styleTabItemDefault}">
                <TextBlock>The content for Options tab.</TextBlock>
            </TabItem>
            <TabItem Header="Tools" Style="{DynamicResource ResourceKey=styleTabItemRight}">
                <TextBlock>The content for Tools tab.</TextBlock>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

Then you have it now, we are finish designing the TabControl.

4 comments:

  1. I was wondering if it is possible to change the text color from white to another color when that tab is active?

    ReplyDelete
  2. Thank you for this great article. very useful.

    ReplyDelete
  3. hello. i have problem. iam testing your code but my visual studio not open. where is problem can you help me pls ?

    ReplyDelete

Place your comments and ideas