How to Add Close and Create Buttons to the Tab Headers using MVVM approach

The goal of this tutorial is to create a RadTabControl with closable tab items using an MVVM approach.

It demonstrates how to close and create new items using close and add buttons placed in the tab header as shown on the snapshot bellow: Rad Tab Control How To Add Close Buttons MVVM

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

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

  • Then we need to define our ViewModels. We can start by creating a class to describe the RadTabItems - TabViewModel exposing a Header property. As we're taking an MVVM approach to implement the Add and Close buttons functionality, we'll also have to set up Add and Close commands in the TabViewModel.

public class MainViewModel 
{ 
    public MainViewModel() 
    { 
        this.Tabs = new ObservableCollection<TabViewModel>(); 
        this.AddItem(null); 
    } 
 
    public ObservableCollection<TabViewModel> Tabs 
    { 
        get; 
        private set; 
    } 
 
    public void AddItem(TabViewModel sender) 
    { 
        TabViewModel newTabItem = new TabViewModel(this); 
        newTabItem.Header = "New Tab"; 
        newTabItem.IsSelected = true; 
        if (sender != null) 
        { 
            int insertIndex = this.Tabs.IndexOf(sender) + 1; 
            this.Tabs.Insert(insertIndex, newTabItem); 
        } 
        else 
        { 
            this.Tabs.Add(newTabItem); 
        } 
    } 
 
    public void RemoveItem(TabViewModel tabItem) 
    { 
        this.Tabs.Remove(tabItem); 
        tabItem.Dispose(); 
    } 
} 
Public Class MainViewModel 
    Public Sub New() 
        Me.Tabs = New ObservableCollection(Of TabViewModel)() 
        Me.AddItem(Nothing) 
    End Sub 
 
    Public Property Tabs() As ObservableCollection(Of TabViewModel) 
        Get 
            Return _tabs 
        End Get 
        Private Set(value As ObservableCollection(Of TabViewModel)) 
            _tabs = Value 
        End Set 
    End Property 
    Private _tabs As ObservableCollection(Of TabViewModel) 
 
 
    Public Sub AddItem(sender As TabViewModel) 
        Dim newTabItem As New TabViewModel(Me) 
        newTabItem.Header = "New Tab" 
        newTabItem.IsSelected = True 
        If sender IsNot Nothing Then 
            Dim insertIndex As Integer = Me.Tabs.IndexOf(sender) + 1 
            Me.Tabs.Insert(insertIndex, newTabItem) 
        Else 
            Me.Tabs.Add(newTabItem) 
        End If 
    End Sub 
 
    Public Sub RemoveItem(tabItem As TabViewModel) 
        Me.Tabs.Remove(tabItem) 
        tabItem.Dispose() 
    End Sub 
End Class 
  • After that we can go ahead and create a MainViewModel to define the collection of TabViewModel items which we will use to populate the RadTabControl.ItemsSource. Please note that as the add/close logic will change the RadTabControl.ItemsSource collection, it's best to implement the commands execution methods in this ViewModel as well:

public class TabViewModel : INotifyPropertyChanged, IDisposable 
{ 
    private bool isSelected; 
    private readonly MainViewModel mainViewModel; 
 
    public TabViewModel(MainViewModel mainViewModel) 
    { 
        this.mainViewModel = mainViewModel; 
        this.mainViewModel.Tabs.CollectionChanged += this.Tabs_CollectionChanged; 
 
        this.AddItemCommand = new DelegateCommand( 
            delegate 
            { 
                this.mainViewModel.AddItem(this); 
            }, 
            delegate 
            { 
                return this.mainViewModel.Tabs.Count < 5; 
            }); 
 
        this.RemoveItemCommand = new DelegateCommand( 
            delegate 
            { 
                this.mainViewModel.RemoveItem(this); 
            }, 
            delegate 
            { 
                return this.mainViewModel.Tabs.Count > 1; 
            }); 
    } 
 
    public void Dispose() 
    { 
        this.mainViewModel.Tabs.CollectionChanged -= this.Tabs_CollectionChanged; 
    } 
 
    void Tabs_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
        this.AddItemCommand.InvalidateCanExecute(); 
        this.RemoveItemCommand.InvalidateCanExecute(); 
    } 
 
    public string Header 
    { 
        get; 
        set; 
    } 
 
    public bool IsSelected 
    { 
        get 
        { 
            return this.isSelected; 
        } 
        set 
        { 
            if (this.isSelected != value) 
            { 
                this.isSelected = value; 
                this.OnPropertyChanged("IsSelected"); 
            } 
        } 
    } 
 
    public DelegateCommand AddItemCommand { get; set; } 
    public DelegateCommand RemoveItemCommand { get; set; } 
 
    #region INotifyPropertyChanged 
    public event PropertyChangedEventHandler PropertyChanged; 
 
    private void OnPropertyChanged(string propertyName) 
    { 
        if (this.PropertyChanged != null) 
        { 
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
        } 
    } 
} 
Public Class TabViewModel 
    Implements INotifyPropertyChanged 
    Implements IDisposable 
    Private _isSelected As Boolean 
    Private ReadOnly mainViewModel As MainViewModel 
 
    Public Sub New(mainViewModel As MainViewModel) 
        Me.mainViewModel = mainViewModel 
        Me.mainViewModel.Tabs.CollectionChanged += Me.Tabs_CollectionChanged 
 
        Me.AddItemCommand = New DelegateCommand(Function() Do 
        Me.mainViewModel.AddItem(Me) 
        End Function, Function() Me.mainViewModel.Tabs.Count < 5) 
 
        Me.RemoveItemCommand = New DelegateCommand(Function() Do 
            Me.mainViewModel.RemoveItem(Me) 
        End Function, Function() Me.mainViewModel.Tabs.Count > 1) 
    End Sub 
 
    Public Sub Dispose() 
        Me.mainViewModel.Tabs.CollectionChanged -= Me.Tabs_CollectionChanged 
    End Sub 
 
    Private Sub Tabs_CollectionChanged(sender As Object, e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) 
        Me.AddItemCommand.InvalidateCanExecute() 
        Me.RemoveItemCommand.InvalidateCanExecute() 
    End Sub 
 
    Public Property Header() As String 
        Get 
            Return _header 
        End Get 
        Set(value As String) 
            _header = Value 
        End Set 
    End Property 
    Private _header As String 
 
    Public Property IsSelected() As Boolean 
        Get 
            Return Me._isSelected 
        End Get 
        Set(value As Boolean) 
            If Me._isSelected <> value Then 
                Me._isSelected = value 
                Me.OnPropertyChanged("IsSelected") 
            End If 
        End Set 
    End Property 
 
    Public Property AddItemCommand() As DelegateCommand 
        Get 
            Return _addItemCommand 
        End Get 
        Set(value As DelegateCommand) 
            _addItemCommand = Value 
        End Set 
    End Property 
    Private _addItemCommand As DelegateCommand 
    Public Property RemoveItemCommand() As DelegateCommand 
        Get 
            Return _removeItemCommand 
        End Get 
        Set(value As DelegateCommand) 
            _removeItemCommand = Value 
        End Set 
    End Property 
    Private _removeItemCommand As DelegateCommand 
 
    Public Event PropertyChanged As PropertyChangedEventHandler 
 
    Private Sub OnPropertyChanged(propertyName As String) 
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) 
    End Sub 
End Class 
  • Now that our ViewModels are all in place, we can proceed with the definition of our view. In order to take full advantage of the implemented commands we have to bind the Add/Close Buttons Command properties to the appropriate DelegateCommands definitions.

  • Here is how the Resources section of our view looks like:

<Style x:Key="CloseButton" TargetType="Button"> 
    <Setter Property="Template"> 
        <Setter.Value> 
            <ControlTemplate TargetType="Button"> 
                <Grid Width="14" 
                      Height="14" 
                      Background="Transparent"> 
                    <VisualStateManager.VisualStateGroups> 
                        <VisualStateGroup x:Name="CommonStates"> 
                            <VisualState x:Name="Normal" /> 
                            <VisualState x:Name="MouseOver"> 
                                <Storyboard> 
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusEllipse" Storyboard.TargetProperty="(UIElement.Visibility)"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Visible</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="FocusEllipse" 
                                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                                                    To="#FFDC3030" /> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="path" 
                                                    Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" 
                                                    To="White" /> 
                                </Storyboard> 
                            </VisualState> 
                            <VisualState x:Name="Pressed"> 
                                <Storyboard> 
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusEllipse" Storyboard.TargetProperty="(UIElement.Visibility)"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Visible</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="FocusEllipse" 
                                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                                                    To="Black" /> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="path" 
                                                    Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" 
                                                    To="White" /> 
                                </Storyboard> 
                            </VisualState> 
                            <VisualState x:Name="Disabled" /> 
                        </VisualStateGroup> 
                        <VisualStateGroup x:Name="FocusStates"> 
                            <VisualState x:Name="Focused" /> 
                            <VisualState x:Name="Unfocused" /> 
                        </VisualStateGroup> 
                    </VisualStateManager.VisualStateGroups> 
                    <Ellipse x:Name="FocusEllipse" 
                             Fill="#FFF13535" 
                             Visibility="Collapsed" /> 
                    <Path x:Name="path" 
                          HorizontalAlignment="Center" 
                          VerticalAlignment="Center" 
                          Data="{TemplateBinding Content}" 
                          Stroke="#FF898888" 
                          StrokeThickness="1" /> 
                </Grid> 
            </ControlTemplate> 
        </Setter.Value> 
    </Setter> 
</Style> 
<Style x:Key="AddButton" TargetType="Button"> 
    <Setter Property="Template"> 
        <Setter.Value> 
            <ControlTemplate TargetType="Button"> 
                <Grid Width="14" 
                      Height="14" 
                      Background="Transparent"> 
                    <VisualStateManager.VisualStateGroups> 
                        <VisualStateGroup x:Name="CommonStates"> 
                            <VisualState x:Name="Normal" /> 
                            <VisualState x:Name="MouseOver"> 
                                <Storyboard> 
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusEllipse" Storyboard.TargetProperty="(UIElement.Visibility)"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Visible</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="FocusEllipse" 
                                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                                                    To="#FF1CC81F" /> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="path" 
                                                    Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" 
                                                    To="White" /> 
                                </Storyboard> 
                            </VisualState> 
                            <VisualState x:Name="Pressed"> 
                                <Storyboard> 
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusEllipse" Storyboard.TargetProperty="(UIElement.Visibility)"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Visible</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="FocusEllipse" 
                                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                                                    To="Black" /> 
                                    <ColorAnimation Duration="0" 
                                                    Storyboard.TargetName="path" 
                                                    Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" 
                                                    To="White" /> 
                                </Storyboard> 
                            </VisualState> 
                            <VisualState x:Name="Disabled" /> 
                        </VisualStateGroup> 
                        <VisualStateGroup x:Name="FocusStates"> 
                            <VisualState x:Name="Focused" /> 
                            <VisualState x:Name="Unfocused" /> 
                        </VisualStateGroup> 
                    </VisualStateManager.VisualStateGroups> 
                    <Ellipse x:Name="FocusEllipse" 
                             Fill="#FFF13535" 
                             Visibility="Collapsed" /> 
                    <Path x:Name="path" 
                          HorizontalAlignment="Center" 
                          VerticalAlignment="Center" 
                          Data="{TemplateBinding Content}" 
                          Stroke="#FF898888" 
                          StrokeThickness="1" /> 
                </Grid> 
            </ControlTemplate> 
        </Setter.Value> 
    </Setter> 
</Style> 
<DataTemplate x:Key="TabItemTemplate"> 
    <StackPanel VerticalAlignment="Center" Orientation="Horizontal"> 
        <TextBlock Text="{Binding Header}" /> 
        <Button Margin="10,0,0,0" 
                Command="{Binding RemoveItemCommand}" 
                Content="M0,0 L6,6 M6, 0 L0,6" 
                Style="{StaticResource CloseButton}" 
                ToolTipService.ToolTip="Remove item" /> 
        <Button Command="{Binding AddItemCommand}" 
                Content="M4,0 L4,8 M0, 4 L8,4" 
                Style="{StaticResource AddButton}" 
                ToolTipService.ToolTip="Add new item" /> 
    </StackPanel> 
</DataTemplate> 
<DataTemplate x:Key="ContentTemplate"> 
    <Grid /> 
</DataTemplate> 
<Style TargetType="telerik:RadTabItem"> 
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
</Style> 
  • And here is the RadTabControl definition:

<telerik:RadTabControl x:Name="tabControl" 
                     Width="600" 
                     Height="300" 
                     ContentTemplate="{StaticResource ContentTemplate}" 
                     ItemTemplate="{StaticResource TabItemTemplate}" 
                     ItemsSource="{Binding Tabs}" 
                     OverflowMode="Wrap" /> 

Please note that in the above sample we have defined custom styles for the Button controls which you can remove or modify accordignly to your requirements.

See Also

In this article
Not finding the help you need? Improve this article