Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  96 Posts | 5 Stories | 258 Comments | 369 Trackbacks

News

Spotkasz mnie na CodeCamp'09 View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

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.

# re: WPF TabItems With Close Button 11/1/2008 9:17 AM kreditrechner
Looks wonderful, but can I use this in conjunction with the binding to TabControl.ItemSource?

# re: WPF TabItems With Close Button 11/14/2008 8:18 PM Jason
A common need is to also support CTRL-F4 to close an individual tab. I've used your sample (wonderful by the way, thank you!), and added the key binding to the CloseableTabItem.cs source file:


public CloseableTabItem()
{
// Associate CTRL+F4 with closing the tab.
AddCloseKeyBinding();
}

/// <summary>
/// Adds CTRL-F4 key binding for close in order to raise the CloseTab event.
/// </summary>
private void AddCloseKeyBinding()
{
// Associate CTRL+F4 with ApplicationCommands.Close.
KeyBinding closeCmdKeyBinding = new KeyBinding(
ApplicationCommands.Close,
Key.F4,
ModifierKeys.Control);

// Add the binding to the control
this.InputBindings.Add(closeCmdKeyBinding);

// Create a delegate to handle the execution of ApplicationCommands.Close.
this.CommandBindings.Add(new CommandBinding(
ApplicationCommands.Close,
delegate
{
// Raise the same event as clicking the close button.
this.RaiseEvent(new RoutedEventArgs(CloseTabEvent, this));
}
));
}

# re: WPF TabItems With Close Button 2/2/2009 8:13 PM Cristian
Thank you Szymon and also Jason. It works "beautifully"... Cheers!

# re: WPF TabItems With Close Button 2/7/2009 11:42 AM Rahul
Wouldn't it be easier just to add the button through the DataTemplate for the header (HeaderTemplate). A custom class seems to be overkill for adding a simple button which is easily achieved through the existing style framework.

# re: WPF TabItems With Close Button 2/13/2009 11:20 AM Manju
Hi
Is there a way to select previous tabitem on close of current tabitem?

# re: WPF TabItems With Close Button 3/9/2009 7:24 PM Udayakiran
In this method, the tab page is removed from items... But the object is still present in memory and poses a problem when recreating the tab page... any idea how to fix that?

# re: WPF TabItems With Close Button 3/19/2009 11:55 AM Василий
а по русски! нифига ж не понятно.

# re: WPF TabItems With Close Button 3/30/2009 2:01 AM Monica
Thank You!!! This worked really well for me with no changes. I appreciate you sharing your efforts!

# re: WPF TabItems With Close Button 4/4/2009 2:15 AM Carlos Guaneme
Great work. Thanks for sharing this.

# re: WPF TabItems With Close Button 8/6/2009 3:38 AM Francesco
Hi, Where can i find more info about "TSRI project" and "opens the tab contents as floating window". ?

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: