There are a number of good articles out there about creating closeable tabs in WPF, but they’re very complicated. It shouldn’t be that hard! So, I’m going to try to break it down and make it easy for you.
Here’s what we’re going to do:
- Create a new user control for the tab close button
- Create a new class that inherits from TabItem
- Try it out!
Create a new user control for the tab close button
Add a new UserControl to your project. Since it’s a full-blown UserControl, you can easily style the button however you’d like and add whatever additional code-behind logic you need. The important thing about the control is that it raises a “Close” event that can be handled in our custom TabItem control. You can get fancy with your button, but I wanted to keep it simple for this example so I just used a red button with a white X drawn on it.
XAML:
<UserControl x:Class="adamprescott.net.TabbedDocuments.TabCloseButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Button Click="OnClick" Background="Red"> <Path Data="M1,9 L9,1 M1,1 L9,9" Stroke="White" StrokeThickness="2" /> </Button> </UserControl>
Code-behind:
namespace adamprescott.net.TabbedDocuments { using System; using System.Windows; using System.Windows.Controls; public partial class TabCloseButton : UserControl { public event EventHandler Click; public TabCloseButton() { InitializeComponent(); } private void OnClick(object sender, RoutedEventArgs e) { if (Click != null) { Click(sender, e); } } } }
Create a new class that inherits from TabItem
Add a new class to your project that derives from TabItem. My class has a single method, SetHeader. This method accepts the desired header content as an argument and adds it to a collection with our custom close button.
namespace adamprescott.net.TabbedDocuments { using System.Windows; using System.Windows.Controls; public class CloseableTabItem : TabItem { public void SetHeader(UIElement header) { // Container for header controls var dockPanel = new DockPanel(); dockPanel.Children.Add(header); // Close button to remove the tab var closeButton = new TabCloseButton(); closeButton.Click += (sender, e) => { var tabControl = Parent as ItemsControl; tabControl.Items.Remove(this); }; dockPanel.Children.Add(closeButton); // Set the header Header = dockPanel; } } }
Try it out!
Now that all the hard stuff is done, let’s make a sample application to test it out. I have a simple Window with a Button and a TabControl. When the button is clicked, a tab is added with a TextBlock header and a TextBlock content. Each tab can be closed by clicking its close button.
XAML:
<Window x:Class="adamprescott.net.TabbedDocuments.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <DockPanel> <Button DockPanel.Dock="Top" Click="OnPlusTabClick">+Tab</Button> <TabControl Name="uxTabs"> </TabControl> </DockPanel> </Window>
Code-behind:
namespace adamprescott.net.TabbedDocuments { using System.Windows; using System.Windows.Controls; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void OnPlusTabClick(object sender, RoutedEventArgs e) { // Create the header var header = new TextBlock { Text = "Tab!" }; // Create the content var content = new TextBlock { Text = string.Format("Tab numero {0}-o", uxTabs.Items.Count + 1) }; // Create the tab var tab = new CloseableTabItem(); tab.SetHeader(header); tab.Content = content; // Add to TabControl uxTabs.Items.Add(tab); } } }
See? It didn’t have to be that hard!