Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Add/Remove Tabs Dynamically in WPF

4.95/5 (24 votes)
19 Nov 2012CPOL3 min read 198.3K   10K  
This article shows how to achieve add/remove tab feature for TabControl in WPF.

Image 1

Introduction

This article shows how to create dynamic tabs in WPF (Windows Presentation Foundation) similar to the one you see in most web browsers where you have an empty tab at the end to add new tab and a close button on each tab to close the tab. There are several techniques to achieve this. Here, a List of TabItem is used to hold the tabs that are bound to the TabControl. The last item of the List is an empty tab that is used to add new TabItem in the list whenever that is selected. A DataTemplate is used for tab header to add a Delete button in each tab.

XAML

The first step is to define XAML for the TabControl. Set the ItemsSource property to {Binding} whereas the DataContext will be supplied by a List of TabItem in the code-behind. Next add the SelectionChanged event.

XML
<TabControl Name="tabDynamic" ItemsSource="{Binding}" 
SelectionChanged="tabDynamic_SelectionChanged">
</TabControl>  

Define the DataTemplate for tab header under TabControl.Resources tag and set the DataType as TabItem. Use any container to add and align a TextBlock and a delete Button. I used a DockPanel since it's easy to align delete Button to right using DockPanel.Dock property. Add Click event to the button and bind the CommandParameter property to the tab Name. This will be used to identify the tab to be deleted in the Click event handler of the button. Bind Text property of TextBlock to the Header of the TabItem. The Header that is a string value will be set in the code-behind.

XML
<DataTemplate x:Key="TabHeader" DataType="TabItem">
    <DockPanel>
        <Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" 
         Padding="0" Click="btnDelete_Click" 
         CommandParameter="{Binding RelativeSource=
         {RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
            <Image Source="/delete.gif" Height="11" Width="11"></Image>
        </Button>
        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=
                         {x:Type TabItem}}, Path=Header}" />
    </DockPanel>
</DataTemplate>  

This is the complete XAML that also includes the style for a TextBox control that is added to each tab:

XML
<TabControl Name="tabDynamic" ItemsSource="{Binding}" 
 SelectionChanged="tabDynamic_SelectionChanged">
    <TabControl.Resources>
        <DataTemplate x:Key="TabHeader" DataType="TabItem">
            <DockPanel>
                <Button Name="btnDelete" DockPanel.Dock="Right" 
                 Margin="5,0,0,0" Padding="0" Click="btnDelete_Click" 
                 CommandParameter="{Binding RelativeSource=
                 {RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
                    <Image Source="/delete.gif" Height="11" Width="11"></Image>
                </Button>
            <TextBlock Text="{Binding RelativeSource=
            {RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
            </DockPanel>
        </DataTemplate>
        <Style TargetType="TextBox">
            <Setter Property="VerticalAlignment" Value="Stretch"></Setter>
            <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
            <Setter Property="AcceptsReturn" Value="True"></Setter>
            <Setter Property="TextWrapping" Value="WrapWithOverflow"></Setter>
            <Setter Property="MaxLines" Value="5000"></Setter>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" 
             Value="Auto"></Setter>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" 
             Value="Auto"></Setter>
        </Style>
    </TabControl.Resources>
</TabControl>  

Now I explain the code that contains the logic to add and delete TabItems.

Code-behind

In the code-behind file, define two data members. One is _tabItems that is a list of TabItem to hold the tabs and other _tabAdd that is a reference to the last TabItem that is used to add new tab dynamically.

C#
private List<TabItem> _tabItems;
private TabItem _tabAdd;    

Initialize the above data members in the contructor and set the header of the last tab as text "+" or any image you prefer. Add the first TabItem using the function AddItemItem() then bind the TabControl to the List and select first TabItem.

C#
public MainWindow() 
{ 
    try
    {
        InitializeComponent();

        // initialize tabItem array
        _tabItems = new List<TabItem>();

        // add a tabItem with + in header 
        TabItem tabAdd = new TabItem();
        tabAdd.Header = "+";

        _tabItems.Add(tabAdd);

        // add first tab
        this.AddTabItem();

        // bind tab control
        tabDynamic.DataContext = _tabItems;

        tabDynamic.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}  

In the function AddTabItem() used above, add a new TabItem to the list _tabItems and returns the instance of that TabItem. Set the HeaderTemplate as the one we defined above. Give a unique name to the TabItem like "TabXX", where XX is a counter to make the name unique. You can use same unique string as the Header text too. It is important to set Header and Name properties since in the DataTemplate defined above in XAML we bound TextBlock.Text and Button.CommandParameter to these properties. Next, add controls that you want to have in your TabItem and add the TabItem in the list _tabItems. Here, I added a simple TextBox control. Also notice that the new tab is inserted right before the last tab that is used to add new tabs.

C#
private TabItem AddTabItem()
{
    int count = _tabItems.Count;

    // create new tab item
    TabItem tab = new TabItem();
    tab.Header = string.Format("Tab {0}", count);
    tab.Name = string.Format("tab{0}", count);
    tab.HeaderTemplate = tabDynamic.FindResource("TabHeader") as DataTemplate;
 
    // add controls to tab item, this case I added just a text box
    TextBox txt = new TextBox();
    txt.Name = "txt";
    tab.Content = txt;

    // insert tab item right before the last (+) tab item
    _tabItems.Insert(count - 1, tab);
    return tab; 
} 

In the implementation of the SelectionChanged event handler, check if the selected TabItem is the last one then add a new TabItem using AddTabItem() function, rebind the TabControl and select newly added TabItem. For other tabs, you can add your code, if any, for this event.

C#
private void tabDynamic_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    TabItem tab = tabDynamic.SelectedItem as TabItem;

    if (tab != null && tab.Header != null)
    {
        if (tab.Header.Equals(_addTabHeader))
        {
            // clear tab control binding
            tabDynamic.DataContext = null;

            // add new tab
            TabItem newTab = this.AddTabItem();

            // bind tab control
            tabDynamic.DataContext = _tabItems;

            // select newly added tab item
            tabDynamic.SelectedItem = newTab;
        }
        else
        {
            // your code here...
        }
    }
}

Finally, implement the Click event handler of the delete button that was added the DataTemplate. Here, identify the tab to be deleted using CommandParameter that in turn gives the TabItem.Name. You can easily find this tab in TabControl.Items collection using linq. Validate that this tab is not the only tab left in the list, then after confirmation, delete the tab from the list _tabItems and rebind the TabControl. If an active tab is deleted, then select the first tab after rebinding, otherwise select the tab that was selected before deletion.

C#
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    string tabName = (sender as Button).CommandParameter.ToString();

    var item = tabDynamic.Items.Cast<tabitem>().Where
               (i => i.Name.Equals(tabName)).SingleOrDefault();

    TabItem tab = item as TabItem;

    if (tab != null)
    {
        if (_tabItems.Count < 3)
        {
            MessageBox.Show("Cannot remove last tab.");
        }
        else if (MessageBox.Show(string.Format
        ("Are you sure you want to remove the tab '{0}'?", tab.Header.ToString()),
            "Remove Tab", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            // get selected tab
            TabItem selectedTab = tabDynamic.SelectedItem as TabItem;

            // clear tab control binding
            tabDynamic.DataContext = null;

            _tabItems.Remove(tab);

            // bind tab control
            tabDynamic.DataContext = _tabItems;

            // select previously selected tab. if that is removed then select first tab
            if (selectedTab == null || selectedTab.Equals(tab))
            {
                selectedTab = _tabItems[0];
            }
            tabDynamic.SelectedItem = selectedTab;
        }
    }
}

History

  • 18th November, 2012: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)