Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  84 Posts | 5 Stories | 164 Comments | 380 Trackbacks

News

View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

Image Galleries

Blogs I Read

Tools I Use

One of the most common questions I've seen regarding the TabControl in Windows Forms was how to add a close button to each tab (similar to seen on tabs in Internet Explorer 7).

Although there were some solutions available the results weren't quite satisfactory and often requiring to rewrite the whole control from scratch. Recently I faced the same challenge working on the TSRI project. It turned out that in WPF this pretty straightforward task and in this article I'm going to show all the steps required to complete it.

To follow the discussion you can download the demo code first:

Download the source code

We start by creating a new custom control deriving from TabItem that implements this behavior. I'll name it CloseableTabItem.

public class CloseableTabItem : TabItem
{
    static CloseableTabItem()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CloseableTabItem),
            new FrameworkPropertyMetadata(typeof(CloseableTabItem)));
    }
}

The instruction in static constructor informs the system that this element wants to use different style than it's parent. Since we are creating the default theme for the custom control it would be defined in generic\themes.xaml.

Before we add more code let's create the control template first. With the aid of Expression Blend we can easily create a copy of the default template for TabItem control and start from there. The default template consists only of Grid and Border that wrap the ContentPresenter. We need to place additional DockPanel inside this Border to host both the Content and our close Button. Because we will reference this button from code later it's named PART_Close following the WPF naming convention. The button contains the "x" icon defined as Path element. You can see final hierarchy of elements on the image below:

The close button should only show it's border when mouse is over and it shouldn't accept keyboard focus, hence there is additional style called CloseableTabItemButtonStyle. This template consist of Border and ContentPresenter inside of a Grid. The Border is hidden by default and shows only when mouse is over the button. To be consistent with IE7 behavior I've also added triggers to change the "x" icon fill color to red when mouse is over the button or when it's pressed.

We are now ready to test these templates. To use our new control first the containing namespace must be mapped to a XML namespace. We don't need to reference the resource dictionary because it is declared as default theme for the control. Here is example markup for the main window of the application:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CloseableTabItemDemo"
    x:Class="CloseableTabItemDemo.MainWindow"
    Title="CloseableTabItem Demo" Height="300" Width="500"
    >
    <Grid>
      <TabControl Margin="5">
        <local:CloseableTabItem Header="TabItem 1"/>
        <local:CloseableTabItem Header="TabItem 2" />
        <local:CloseableTabItem Header="TabItem 3" />
        <TabItem Header="TabItem 4" />
      </TabControl>
    </Grid>
</Window>

Note that we can freely mix the regular TabItems with our custom controls. Here is the result when we run this application:

Having all the visuals in place lets switch back to Visual Studio and finish the remining code. I choose to publish the Close button click event as the a routed event on the control. Of course I could handle it directly in the code of the control but this way I have more control on how to handle it in the application (for example I could display a confirmation dialog before closing the tab). Alternatively I could also create a custom Command and bind it directly to the close button. This would allow me to declare everything in XAML markup but I think event would be easier to use in this case. So below is the declaration for the CloseTab event:

public static readonly RoutedEvent CloseTabEvent =
    EventManager.RegisterRoutedEvent("CloseTab", RoutingStrategy.Bubble,
        typeof(RoutedEventHandler), typeof(CloseableTabItem));

public event RoutedEventHandler CloseTab
{
    add { AddHandler(CloseTabEvent, value); }
    remove { RemoveHandler(CloseTabEvent, value); }
}

To raise the event I need first to attach a handler to the Button's Click event. I can do it easily by overriding the ... method. This is where I use the PART_Close name mentioned earlier to find the button declared in template by using the GetTemplateChild method. The event handler simply raises the new event.

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    Button closeButton = base.GetTemplateChild("PART_Close") as Button;
    if (closeButton != null)
        closeButton.Click += new System.Windows.RoutedEventHandler(closeButton_Click);
}

void closeButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
    this.RaiseEvent(new RoutedEventArgs(CloseTabEvent, this));
}

So the only thing left is to actually handle this event. This will be done in the MainWindow's code behind. Normally I would have to attach handlers for this event to each tab I created but since this is a routed event (with bubble strategy) I can also attach it once on any of it's parents (up to the Window itself). Closing the tab is done by finding the parent TabControl removing the source tab from it's Items collection.

public partial class MainWindow : System.Windows.Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.AddHandler(CloseableTabItem.CloseTabEvent, new RoutedEventHandler(this.CloseTab));
    }

    private void CloseTab(object source, RoutedEventArgs args)
    {
        TabItem tabItem = args.Source as TabItem;
        if (tabItem != null)
        {
            TabControl tabControl = tabItem.Parent as TabControl;
            if (tabControl != null)
                tabControl.Items.Remove(tabItem);
        }
    }
}

That's all we need to do. Now we have customized tab items with fully working tab control. And as you can see this is relatively easy to implement so nothing prevents adding more buttons or other elements on the tabs. For example in the TSRI project we have additional button that opens the tab contents as floating window. 

posted on Sunday, April 08, 2007 4:48 PM

Feedback

# Links (4/8/2007) 4/25/2007 9:40 PM Member Blogs
.NET If Visual Studio takes ages to load Windows Workflow Foundation Web Workflow Approvals Starter Kit

# re: WPF TabItems With Close Button 8/15/2007 12:08 AM Bryan Livingston
Thanks. This came in handy.

# re: WPF TabItems With Close Button 8/31/2007 9:54 PM Chuck McKinnon
Thanks -- very useful, and educational to walk through your XAML. Just one typo correction, near the top of this page: the default theme will be in themes/generic.xaml, rather than generic/themes.xaml.

# re: WPF TabItems With Close Button 9/3/2007 8:30 PM Szymon
Chuck,
I'm glad my post was helpful for you and thanks for pointing out the error - it's already corrected.

# re: WPF TabItems With Close Button 11/29/2007 3:44 PM Sean Cullinan
Thanks Szymon!

For anyone having problems translating the code to VB I posted how I did it on my blog at http://www.blendblog.net/Blendblognet/tabid/36/EntryID/7/Default.aspx.

# re: WPF TabItems With Close Button 12/10/2007 7:36 PM Bryan
I tried your code in my project, and I cannot get the tabs to show up. If I comment out the line that changes the style it works like an old tab. I can't see anything wrong? Maybe I am missing something else?

# re: WPF TabItems With Close Button 12/10/2007 7:58 PM Bryan
Ok - so the problem is I tried to move the control style xaml into a directory called "Resources\Styles" and renamed it to "CloseableTabItem.xaml" - apparently it must be in a directory called "themes" and it must be named "generic.xaml". I don't see anywhere in the code that says it must be located here or that name but it will only work like that. I'd like to find a way to change this as it would be the only file outside of my directory structure for my project.

# re: WPF TabItems With Close Button 12/10/2007 9:24 PM Bryan
Ok - App.xaml file:

<Application x:Class="WorldVibeCoreFrameworkTool.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary Source="Resources/Styles/ClosableTabItem.xaml" />
</Application.Resources>
</Application>

Works like a champ!

# re: WPF TabItems With Close Button 12/17/2007 8:28 PM Jonathan
I don't get it. Did you create this generic.xaml file by hand, or did you use blend to do it? When I open up the solution in blend i don't see anywhere to edit the PART_close template. All I see is generic.xaml and MainWindow.xaml. when i try to open up generic.xaml, it says "generic.xaml cannot bo edited in the design view. To edit resources, select from the Resources panel."

I am really new to wpf and any more details would be great. Thanks.

# re: WPF TabItems With Close Button 12/17/2007 10:22 PM Jonathan
Was the original XAML for the CloseableTabItem created and then pasted into the generic.xaml ResourceDictionary file?

# re: WPF TabItems With Close Button 12/18/2007 12:17 PM Horst Klein
Hi sound great. But cant test it. The Link to your Downloadserver is not available.
can you send me the sample.

Best regards
Horst

# re: WPF TabItems With Close Button 12/21/2007 1:27 PM Didier
Hi,

Same problem, link not working anymore to source code ...

Thanks
Bye
Didier

# re: WPF TabItems With Close Button 12/31/2007 11:44 AM Szymon
I've moved the file to SkyDrive - look for the new link above.

# re: WPF TabItems With Close Button 1/2/2008 6:52 PM Ronald Siegel
Thanks a lot.

Ronald S.

# re: WPF TabItems With Close Button 1/4/2008 10:06 AM Mark
Hi,
works fine, but why are you delegating
the removing of the (Custom)TabItem?

My Way:
void closeButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
TabControl tabControl = this.Parent as TabControl;
if (tabControl != null)
tabControl.Items.Remove(this);
}

Mark

# re: WPF TabItems With Close Button 1/12/2008 11:26 AM smark
I was wondering if this could be adapted to CAB's TabWorkspace. Just changing the TabSmartpartInfo doesn't seem to work though. Perhaps there is some connection here with the object builder. Any ideas.

# re: WPF TabItems With Close Button 2/7/2008 9:25 AM David Schmitt
Hi Szymon!

Great code! I can't seem to figure out how to use it in my case though, since I'm binding the TabControl's ItemSource directly to my data and I'm stumped how that should work.

Any hints there?

Regards, David S.

# re: WPF TabItems With Close Button 2/20/2008 3:12 PM Leonard Brünings
Hi,

a really nice control. The only problem I have is that the Designer cannot create an instance, if I start the program the TabControl works nicely.

Any ideas?

Best Regards,
Leonard Brünings

# re: WPF TabItems With Close Button 3/19/2008 4:48 PM Roman S. Golubin
Looks wonderful, but can I use this in conjunction with the binding to TabControl.ItemSource?

# re: WPF TabItems With Close Button 4/8/2008 1:26 PM gnobber
hi, thanks for sharing the code. Currently using it in one of my projects. But I can't close a tabItem programatically (File -> Close). Any ideas?

# re: WPF TabItems With Close Button 4/11/2008 4:06 AM Adam Selene
FYI, I had to change:

TabItem tabItem = args.Source as TabItem;

to

TabItem tabItem = args.OriginalSource as TabItem;

Perhaps it's because I'm in a UserControl class and not a Window class.


# re: WPF TabItems With Close Button 4/16/2008 12:21 PM Quang Tran Minh
I also have doubt that this will support data binding :(

# re: WPF TabItems With Close Button 4/16/2008 12:25 PM Quang Tran Minh
You guys may want to check this alternative:

http://www.codeproject.com/KB/WPF/WpfTabControl.aspx?msg=2509567#xx2509567xx

# re: WPF TabItems With Close Button 5/16/2008 3:54 PM stephanie
Hello !

Thank you for your example, i have a question ! I want to use the code outside the local project. But, when i try to set the generic.xaml file in Resource (Build Action) i have an error of parsing. I think is cause of the local link of the closeableItem class in the resourceDictionnary !
How i can apply the style without having a reference of the type of the class in my resourceDictionnary ?

Thank you so much for helping me !!!

# re: WPF TabItems With Close Button 6/13/2008 11:33 AM Gareth
You could also add a code behind file for the resource dictionary and handle the close_click event and accessing the tab item through the sender templated parent.

Post Feedback

Title:
Name:
Email: (never displayed)
Url:
Comments: 
Please add 1 and 2 and type the answer here: