Remove tabs from a WPF TabControl at runtime in C#

[WPF TabControl]

The example Add tabs to a WPF TabControl at runtime in C# shows how you can add tabs to a control at runtime. You would think removing tabs would be easier, and it is in a way. You simply remove the TabItem objects from the TabControl.

The most obvious way to do that, however, is to add new controls to the TabItem controls. That means you also need to add those controls when you create a new tab so case you want to remove it later.

The program’s XAML code includes some styles to make give tabs the same size and to position the labels inside them. The following code shows one of the initial tabs.

<TabItem Name="tabItem1"
    Image.PreviewMouseDown="TabItem_RemoveClicked">
    <TabItem.Header>
        <Grid Style="{StaticResource headerGridStyle}">
            <Label Content="Tab 1"
                Style="{StaticResource headerLabelStyle}"/>
            <Image Style="{StaticResource deleteImageStyle}"/>
        </Grid>
    </TabItem.Header>
    <Label Background="LightGreen"
        Content="This is the content on tab 1"/>
</TabItem>

The TabItem control includes an Image.PreviewMouseDown event handler. To close a tab, the user will click on the Image control (described shortly) that’s inside the TabItem. When the user does this, the PreviewMouseDown event tunnels down into the user interface tree and is caught by this event handler (which I’ll describe in a minute).

The TabItem.Header contains a Grid that holds a Label and an Image. The Label just displays the tab’s header text. The Image displays the X that the user can click to close the tab.

The TabItem control’s contents just includes a Label. Normally you would probably put a container here such as a grid and then place other controls inside the container.

The following code shows the Image.PreviewMouseDown event handler.

// Remove the clicked tab.
private void TabItem_RemoveClicked(object sender,
    MouseButtonEventArgs e)
{
    // Find the clicked Tab.
    TabItem tab_item = sender as TabItem;

    // Remove the TabItem.
    tabMain.Items.Remove(tab_item);

    e.Handled = true;
}

This event handler is called by the TabItem object that registered it. The code converts the sender parameter (which is the TabItem that raised the event) into a TabItem and then removes it from the TabControl.

The code finishes by setting e.Handled to true so the event doesn’t travel any further through the user interface tree.

That takes care of removing a tab. Now we need to modify the way the previous example adds a tab.

The following code executes when you click the program’s + button.

// Add a tab to the TabControl.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
    TabItem tab_item = new TabItem();
    tab_item.AddHandler(
        Image.PreviewMouseDownEvent,
        new RoutedEventHandler(PreviewMouseDown_Handler));
    tabMain.Items.Add(tab_item);

    Grid grid = new Grid();
    grid.Width = 70;
    grid.Height = 30;

    Label label = new Label();
    label.Content = "New Tab";
    label.HorizontalAlignment = HorizontalAlignment.Left;
    label.VerticalAlignment = VerticalAlignment.Center;
    grid.Children.Add(label);

    Image image = new Image();
    
    image.HorizontalAlignment = HorizontalAlignment.Right;
    image.VerticalAlignment = VerticalAlignment.Top;
    image.Source = new BitmapImage(new Uri(
        "pack://application:,,,/howto_wpf_remove_tabs;component/Remove.png"));
    image.Stretch = Stretch.None;
    image.Cursor = Cursors.Cross;
    image.Width = 10;
    image.Height = 10;
    grid.Children.Add(image);

    tab_item.Header = grid;

    Label content = new Label();
    content.Content = "This is the new tab's content";
    tab_item.Content = content;
}

This code mostly just creates the controls needed to build the tab, its header, and its contents. The most interesting part is where the code gives the new TabItem a new event handler to catch the attached event Image.PreviewMouseDownEvent. The code uses the item’s AddHandler method to add a new RoutedEventHandler. Unfortunately the signature for that type of event handler doesn’t exactly match the signature of the previously built event handler. The routed event handler’s second parameter has type RoutedEventArgs while the previous handler’s second parameter has the more specific type MouseButtonEventArgs.

You could change the previous event handler so it takes the less specific type. Rather than doing that, I simply created a new event handler to catch the new type of event.

// Parameter e is actually a MouseButtonEventArgs.
private void PreviewMouseDown_Handler(object sender,
    RoutedEventArgs e)
{
    TabItem_RemoveClicked(sender, e as MouseButtonEventArgs);
}

This code simply converts the second parameter into the more specific type and then invokes the previously defined TabItem_RemoveClicked event handler.

To summarize, when you click the + button, the program adds a new tab with controls and the Preview_MouseDown event handler. When you click a tab's X button, the Preview_MouseDown event handler removes that TabItem from the TabControl.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.

This entry was posted in controls, user interface and tagged , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *