Skip to main content

...

....

C# WPF Tutorial - Tab Control : Inside and Out [Intermediate]


When it comes to the WinForm's Tab Control, there was a lot left to be desired. If you had to make major changes to either looks or functionality, you were better off just writing your own tab control completely from scratch. The WPF Tab Control makes major strides in the right direction, and because of the power of WPF styles and control templates, you pretty much have complete control over how the tab control looks and feels. This tutorial is going to introduce you to the tab control and demonstrate how to re-skin it look like how you want.

Let's start off easy by simply putting a regular old tab control in a window and adding a couple of tabs.
<Window x:Class="TabControlTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Tabs" Height="281" Width="454">
  <Grid>
    <TabControl>
      <TabItem Header="Cheese">
        The Cheese Tab
      </TabItem>
      <TabItem Header="Pepperoni">
        The Pepperoni Tab
      </TabItem>
      <TabItem Header="Mushrooms">
        The Mushrooms Tab
      </TabItem>
    </TabControl>
  </Grid>
</Window>
 
The above code gives you a pretty standard looking tab control like the one pictured below.

Basic Tab Control

Like most other WPF controls, the contents of a TabItem can be most any other WPF control. Let's look at a tab with an image set as the content.
<Window x:Class="TabControlTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Tabs" Height="281" Width="454">
  <Grid>
    <TabControl>
      <TabItem Header="Cheese">
        The Cheese Tab
      </TabItem>
      <TabItem Header="Pepperoni">
        <Image Source="pepperoni.jpg" />
      </TabItem>
      <TabItem Header="Mushrooms">
        The Mushrooms Tab
      </TabItem>
    </TabControl>
  </Grid>
</Window>
 
That should look something like the following:

Pepperoni Tab Control

That's enough of the simple stuff. If all you want is a basic tab control with some content on each tab, the above code will do everything you need. Let's look at some more interesting stuff now. Just like the content of the TabItem, the Header property of the TabItem can also have other WPF controls. Let's put an image in each of the tabs.
<Window x:Class="TabControlTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Tabs" Height="281" Width="454">
  <Grid>
    <TabControl>
      <TabItem>
        <TabItem.Header>
          <StackPanel Orientation="Horizontal">
            <Image Height="18" Source="cheese.jpg" />
            <TextBlock Text="Cheese" Margin="2,0,0,0" VerticalAlignment="Center" />
          </StackPanel>
        </TabItem.Header>
      </TabItem>
      <TabItem>
        <TabItem.Header>
          <StackPanel Orientation="Horizontal">
            <Image Height="18" Source="pepperoni.jpg" />
            <TextBlock Text="Pepperoni" Margin="2,0,0,0" VerticalAlignment="Center" />
          </StackPanel>
        </TabItem.Header>
      </TabItem>
      <TabItem>
        <TabItem.Header>
          <StackPanel Orientation="Horizontal">
            <Image Height="18" Source="mushrooms.jpg" />
            <TextBlock Text="Mushrooms" Margin="2,0,0,0" VerticalAlignment="Center" />
          </StackPanel>
        </TabItem.Header>
      </TabItem>
    </TabControl>
  </Grid>
</Window>

And that makes something that looks like the image below.

Tab Control With Images

With the techniques above, you should be able to do almost anything you need with a tab control. What you can't do, however, is change how the underlining tab control looks. Fortunately, WPF's style system makes modifying how the tab control looks fairly easy. By default, the color of the tabs themselves will follow the theme of the Windows machine they're running on. Let's start off by modifying the color and shape of the tabs.

The first thing we need to do is define a style for the TabItem control. Styles for WPF are similar to what CSS is for web pages, except much more powerful.
<Window x:Class="TabControlTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Tabs" Height="281" Width="454">
  <Window.Resources>
    <Style TargetType="{x:Type TabItem}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TabItem}">
            <Grid>
              <Border 
                  Name="Border"
                  Background="LightBlue"
                  BorderBrush="Black" 
                  BorderThickness="1,1,1,1" 
                  CornerRadius="6,6,0,0" >
                <ContentPresenter x:Name="ContentSite"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    ContentSource="Header"
                    Margin="12,2,12,2"/>
              </Border>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>
  <Grid>
      <TabControl>
        <TabItem Header="Cheese" />
        <TabItem Header="Pepperoni" />
        <TabItem Header="Mushrooms" />
      </TabControl>
  </Grid>
</Window>
 
Styles are defined up in the resources for your control - in this case the Window that holds my tab control. There's a lot going on here, and most of it I stole from this MSDNarticle. Let's step through this tag-by-tag.
<Style TargetType="{x:Type TabItem}">
 
Here we are defining a style for a specific type of control - the TabItem. Any TabItem that appears in this window will use this style.
<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type TabItem}">

This style is simply setting the Template property of the TabItem. The template can hold most types of WPF controls, so you can make it look pretty much like anything you want.
<Grid>
  <Border 
      Name="Border"
      Background="LightBlue"
      BorderBrush="Black" 
      BorderThickness="1,1,1,1" 
      CornerRadius="6,6,0,0" >
    <ContentPresenter x:Name="ContentSite"
        VerticalAlignment="Center"
        HorizontalAlignment="Center"
        ContentSource="Header"
        Margin="12,2,12,2"/>
  </Border>
</Grid>
 
For this template, we simply put a grid and a border to give the tab a background color and some rounded corners. The ContentPresenter is there to display the content of our TabItem - in this case whatever you put in the Header. With this style applied to the Tab Control, our application now looks like the following:

Styled Tab Control

Not too bad, but you may have noticed that deselected tabs aren't distinguished from selected tabs. This is because when you override the style of a Tab Item, you're now responsible for everything. This is easily implemented with Triggers.
<Style TargetType="{x:Type TabItem}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type TabItem}">
        <Grid>
          <Border 
              Name="Border"
              Background="LightBlue"
              BorderBrush="Black" 
              BorderThickness="1,1,1,1" 
              CornerRadius="6,6,0,0" >
            <ContentPresenter x:Name="ContentSite"
                VerticalAlignment="Center"
                HorizontalAlignment="Center"
                ContentSource="Header"
                Margin="12,2,12,2"/>
          </Border>
        </Grid>
        <ControlTemplate.Triggers>
          <Trigger Property="IsSelected" Value="True">
            <Setter TargetName="Border" Property="Background" Value="LightBlue" />
          </Trigger>
          <Trigger Property="IsSelected" Value="False">
            <Setter TargetName="Border" Property="Background" Value="LightGray" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
 
When a TabItem is clicked, its IsSelected property gets set to true. So what we have to do is add triggers watching the IsSelected property. When the property becomes true, the background color is set to light blue. When it's false, the background color is changed to light gray. Now we've got something that looks like this:

Tabs With Triggers

I think we've got pretty good control over the tabs. Now I want to change how the background and borders look on the tab contents. The style for the tab control is very similar to what we just did for the tab items.
<Style  TargetType="{x:Type TabControl}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type TabControl}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>
          <TabPanel 
              Grid.Row="0"
              Panel.ZIndex="1" 
              Margin="0,0,4,-1" 
              IsItemsHost="True"
              Background="Transparent" />
          <Border 
              Grid.Row="1"
              BorderBrush="Black" 
              BorderThickness="1" 
              CornerRadius="0, 12, 12, 12" >
            <Border.Background>
              <LinearGradientBrush>
                <GradientStop Color="LightBlue" Offset="0" />
                <GradientStop Color="White" Offset="1" />
              </LinearGradientBrush>
            </Border.Background>
            <ContentPresenter ContentSource="SelectedContent" />
          </Border>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
 
With that code we now have a tab control that looks like this:

Tab Control With Styled Contents

I think that about does it for the WPF TabControl. In this tutorial you've learned how to create and populate a basic tab control as well as more advanced techniques for styling the tabs and tab contents. I think the learning curve for WPF styles is a little steep, but once you understand it, you can see how powerful it is. The best thing, however, is that I didn't have to write any C# code to skin my tab control.

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV