Edit this page

How to Add Close Button to the Tab Headers

The goal of this tutorial is to create a tab control with closable tab items. The tab items can be closed using close button placed in the tab header as shown on the snapshot bellow.

For the purpose of this example, you will need to create an empty Silverlight Application project and open it in Visual Studio.

If you copy and paste the source code directly from this XAML examples, don't forget to change xmlns:example alias to import the namespace used in your project.

  • The first step is to add references to the assemblies Telerik.Windows.Controls and Telerik.Windows.Controls.Navigation.

  • In order to create closable tab control, we are going to make use of the routed events mechanism. That is why you will create a helper class named RoutedEventHelper. It will contain the declarations of both the close tab routed event and the attached property for enabling the routed event for the close button. Here is how this class looks like:

public class RoutedEventHelper
{
    //Create the routed event:
    public static readonly RoutedEvent CloseTabEvent = EventManager.RegisterRoutedEvent(
        "CloseTab",
        RoutingStrategy.Bubble,
        typeof(RoutedEventHandler),
        typeof(RoutedEventHelper));
    //Add an attached property:
    public static bool GetEnableRoutedClick(DependencyObject obj)
    {
        return (bool)obj.GetValue(EnableRoutedClickProperty);
    }
    public static void SetEnableRoutedClick(DependencyObject obj, bool value)
    {
        obj.SetValue(EnableRoutedClickProperty, value);
    }
    // Using a DependencyProperty as the backing store for EnableRoutedClick.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EnableRoutedClickProperty = DependencyProperty.RegisterAttached(
        "EnableRoutedClick",
        typeof(bool),
        typeof(RoutedEventHelper),
        new System.Windows.PropertyMetadata(OnEnableRoutedClickChanged));
    private static void OnEnableRoutedClickChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var newValue = (bool)e.NewValue;
        var button = sender as Button;
        if (button == null)
            return;
        if (newValue)
            button.Click += new RoutedEventHandler(OnButtonClick);
    }
    static void OnButtonClick(object sender, RoutedEventArgs e)
    {
        var control = sender as Control;
        if (control != null)
        {
            control.RaiseEvent(new RadRoutedEventArgs(RoutedEventHelper.CloseTabEvent, control));
        }
    }
}
Public Class RoutedEventHelper
    'Create the routed event:'
        Public Shared ReadOnly CloseTabEvent As RoutedEvent = EventManager.RegisterRoutedEvent("CloseTab", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(RoutedEventHelper))
    'Add an atached property:'
    Public Shared Function GetEnableRoutedClick(ByVal obj As DependencyObject) As Boolean
        Return CBool(obj.GetValue(EnableRoutedClickProperty))
    End Function
    Public Shared Sub SetEnableRoutedClick(ByVal obj As DependencyObject, ByVal value As Boolean)
        obj.SetValue(EnableRoutedClickProperty, value)
    End Sub
    ' Using a DependencyProperty as the backing store for EnableRoutedClick. This enables animation, styling, binding, etc...'
    Public Shared ReadOnly EnableRoutedClickProperty As DependencyProperty = DependencyProperty.RegisterAttached("EnableRoutedClick", GetType(Boolean), GetType(RoutedEventHelper), New System.Windows.PropertyMetadata(New PropertyChangedCallback(AddressOf OnEnableRoutedClickChanged)))
    Private Shared Sub OnEnableRoutedClickChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim newValue = CBool(e.NewValue)
        Dim button = TryCast(sender, Button)
        If button Is Nothing Then
            Exit Sub
        End If
        If newValue Then
            AddHandler button.Click, AddressOf OnButtonClick
        End If
    End Sub
    Private Shared Sub OnButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim control = TryCast(sender, Control)
        If control IsNot Nothing Then
            control.RaiseEvent
        End If
    End Sub
End Class

As you can see, you have declared a new routed event called CloseTabEvent of type Telerik.Windows.RoutedEvent. For its creation you have used the static method DeclareRoutedEvent from the class Telerik.Windows.EventManager. This routed event will be raised when the close button of the tab control is clicked by the user.

Another thing that is worth mentioning is the declaration of the attached property EnableRoutedClickProperty. Using this property you can enable the raise of the close routed event for certain button control. See how this property is set from XAML in the next step.

  • Change the XAML of MainPage.xaml to:

<UserControl.Resources>
    <Style x:Key="ClosableStyle" TargetType="telerik:RadTabItem">
        <Setter Property="HeaderTemplate">
            <Setter.Value>
              <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <ContentControl Content="{Binding Title}" />
                            <telerik:RadButton Grid.Column="1" 
                                               Width="16"
                                               Height="16"
                                               Margin="3 0 0 0"
                                               HorizontalAlignment="Center"
                                               VerticalAlignment="Center"
                                               Content="x"
                                               example:RoutedEventHelper.EnableRoutedClick="True"
                                               Padding="0" />
                        </Grid>
               </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding Content}" TextAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
    <telerik:RadTabControl x:Name="tabControl" ItemContainerStyle="{StaticResource ClosableStyle}">
    </telerik:RadTabControl>
</Grid>

In the beginning of the XAML you have imported two namespaces - example and telerik. The example namespace imports all types from the current test project like RoutedEventHelper. The second namespace is telerik and it imports all telerik navigation controls like RadTabControl from the assembly Telerik.Windows.Controls.Navigation.

Later in the UserControl.Resources section you declare a custom item container style, which defines the header and the content templates of the tab items. The header template contains a grid control with the close button on the right side and a content control located on the left side. The routing events for the close button are enabled using your new attached property EnableRoutedClick which is set to True.

  • The last step of this example scenario is to open MainPage.xaml.cs and to:

  • Bind the tab control to an observable collection containing items of the custom type TabItemModel

  • Register the RadTabItem class handler for the routed event CloseTabEvent.

public partial class MainPage : UserControl
{
    ObservableCollection<TabItemModel> tabItemsModel = new ObservableCollection<TabItemModel>();
    public MainPage()
    {
        InitializeComponent();
        this.CreateTabItems();
        EventManager.RegisterClassHandler( typeof(RadTabItem), RoutedEventHelper.CloseTabEvent, new RoutedEventHandler(OnCloseClicked));
    }
    public void OnCloseClicked(object sender, RoutedEventArgs e)
    {
        var tabItem = sender as RadTabItem;
        // Remove the item from the collection the control is bound to
        tabItemsModel.Remove(tabItem.DataContext as TabItemModel);
    }
    private void CreateTabItems()
    {
        // Create items:
        for (int num = 0; num < 5; num++)
        {
            tabItemsModel.Add(new TabItemModel()
            {
                Title = String.Format("Item {0}", num),
                Content = String.Format("Item Content {0}", num)
            });
        }
        // Attach the items:
        tabControl.ItemsSource = tabItemsModel;
    }
}
public class TabItemModel
{
    public String Title
    {
        get;
        set;
    }
    public String Content
    {
        get;
        set;
    }
}
Partial Public Class Page
    Inherits UserControl
    Private tabItemsModel As New ObservableCollection(Of TabItemModel)()
    Public Sub New()
        InitializeComponent()
        Me.CreateTabItems()
        EventManager.RegisterClassHandler(GetType(RadTabItem), RoutedEventHelper.CloseTabEvent, New RoutedEventHandler(AddressOf OnCloseClicked))
    End Sub
    Public Sub OnCloseClicked(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim tabItem = TryCast(sender, RadTabItem)
        ' Remove the item from the collection the control is bound to
        tabItemsModel.Remove(TryCast(tabItem.DataContext, TabItemModel))
    End Sub
    Private Sub CreateTabItems()
        ' Create items:
        For num As Integer = 0 To 4
            Dim itemModel = New TabItemModel()
            itemModel.Content = String.Format("Item Content {0}", num)
            itemModel.Title = String.Format("Item {0}", num)
            tabItemsModel.Add(itemModel)
        Next
        ' Attach the items:
        tabControl.ItemsSource = tabItemsModel
    End Sub
End Class
Public Class TabItemModel
    Private _Title As [String]
    Public Property Title() As [String]
        Get
            Return _Title
        End Get
        Set(ByVal value As [String])
            _Title = value
        End Set
    End Property
    Private _Content As [String]
    Public Property Content() As [String]
        Get
            Return _Content
        End Get
        Set(ByVal value As [String])
            _Content = value
        End Set
    End Property
End Class

See Also

Was this article helpful? Yes No

Give article feedback

Tell us how we can improve this article

Dummy