How to Make the Tab Headers Editable

The goal of this tutorial is to create a tab control with editable headers of the tab items. The idea is to allow runtime change of the tab item's header text as shown on the snapshot below. Silverlight RadTabControl Make the Tab Headers Editable

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.

First add references to the assemblies Telerik.Windows.Controls and Telerik.Windows.Controls.Navigation.

Then create a new Silverlight Templated Control - EditableTabHeader that derives from ContentControl and leave it empty for now.

public class EditableTabHeader : ContentControl 
{ 
    public EditableTabHeader() 
    { 
       this.DefaultStyleKey = typeof(EditableTabHeader); 
    } 
} 
Public Class EditableTabHeader 
    Inherits ContentControl 
    Public Sub New()  
      Me.DefaultStyleKey = GetType(EditableTabHeader) 
    End Sub 
End Class 

Create a new style for the EditableTabHeader control.

<Style TargetType="example:EditableTabHeader"> 
    <Setter Property="Template"> 
        <Setter.Value> 
            <ControlTemplate TargetType="example:EditableTabHeader"> 
                <Grid> 
                    <VisualStateManager.VisualStateGroups> 
                        <VisualStateGroup x:Name="EditStates"> 
                            <VisualState x:Name="EditMode"> 
                                <Storyboard> 
                                    <ObjectAnimationUsingKeyFrames 
                                            Storyboard.TargetName="ContentPresenter" 
                                            Storyboard.TargetProperty="Visibility"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Collapsed</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                    <ObjectAnimationUsingKeyFrames 
                                            Storyboard.TargetName="TextBox" 
                                            Storyboard.TargetProperty="Visibility"> 
                                        <DiscreteObjectKeyFrame KeyTime="0"> 
                                            <DiscreteObjectKeyFrame.Value> 
                                                <Visibility>Visible</Visibility> 
                                            </DiscreteObjectKeyFrame.Value> 
                                        </DiscreteObjectKeyFrame> 
                                    </ObjectAnimationUsingKeyFrames> 
                                </Storyboard> 
                            </VisualState> 
                            <VisualState x:Name="ViewMode"> 
                            </VisualState> 
                        </VisualStateGroup> 
                    </VisualStateManager.VisualStateGroups> 
                    <ContentPresenter Content="{TemplateBinding Content}" 
                            x:Name="ContentPresenter" 
                            ContentTemplate="{TemplateBinding ContentTemplate}" /> 
                    <TextBox Text="{TemplateBinding Content}" x:Name="TextBox" 
                            Visibility="Collapsed" /> 
                </Grid> 
            </ControlTemplate> 
        </Setter.Value> 
    </Setter> 
</Style> 

In the XAML code above we create new style for the EditableTabHeader control and this style will be the default template for that control. The template is made of ContentPresenter, TextBox and a state group EditStates with two new states EditMode and ViewMode. The "EditMode" state contains a storyboard that hides the content presenter control and makes the text box visible, while the ViewMode state does nothing, which means that when the control is in this state it will have its default appearance.

Add the following implementation to the code behind of the EditableTabHeader class.

[TemplateVisualState(GroupName = "EditStates", Name = "EditMode")] 
[TemplateVisualState(GroupName = "EditStates", Name = "ViewMode")] 
public class EditableTabHeader : ContentControl 
{ 
    private TextBox textBox; 
    private DateTime previosLeftClickTime = DateTime.Now; 
    private Point previosLeftClickPoint; 
    private TimeSpan doubleClickSpan = TimeSpan.FromSeconds(0.4); 
    public static readonly DependencyProperty IsInEditModeProperty = DependencyProperty.Register( 
        "IsInEditMode",  
        typeof(bool),  
        typeof(EditableTabHeader),  
        new PropertyMetadata(OnIsInEditModeChanged)); 
 
    public EditableTabHeader() 
    { 
        DefaultStyleKey = typeof(EditableTabHeader); 
    } 
    public override void OnApplyTemplate() 
    { 
        base.OnApplyTemplate(); 
        this.textBox = this.GetTemplateChild("TextBox") as TextBox; 
        this.textBox.LostFocus += new RoutedEventHandler(textBox_LostFocus); 
    } 
    private void textBox_LostFocus(object sender, RoutedEventArgs e) 
    { 
        this.IsInEditMode = false; 
    } 
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) 
    { 
        base.OnMouseLeftButtonDown(e); 
        var currentTime = DateTime.Now; 
        var currentPoint = e.GetPosition(this); 
        var durationBetweenClicks = currentTime - previosLeftClickTime; 
        if (currentPoint == previosLeftClickPoint && durationBetweenClicks < this.doubleClickSpan) 
        { 
            e.Handled = true; 
            this.IsInEditMode = !this.IsInEditMode; 
        } 
        this.previosLeftClickTime = DateTime.Now; 
        this.previosLeftClickPoint = e.GetPosition(this); 
    } 
    public bool IsInEditMode 
    { 
        get { return (bool)GetValue(IsInEditModeProperty); } 
        set { SetValue(IsInEditModeProperty, value); } 
    } 
    private static void OnIsInEditModeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
    { 
        var editableContentControl = sender as EditableTabHeader; 
        var newValue = (bool)e.NewValue; 
        if (!newValue) 
        { 
            editableContentControl.Content = editableContentControl.textBox.Text; 
        } 
        editableContentControl.ChangeVisualStates(); 
    } 
    public void ChangeVisualStates() 
    { 
        if (this.IsInEditMode) 
        { 
            VisualStateManager.GoToState(this, "EditMode", true); 
        } 
        else 
        { 
            VisualStateManager.GoToState(this, "ViewMode", true); 
        } 
    } 
} 
    <TemplateVisualState(GroupName:="EditStates", Name:="EditMode")> 
    <TemplateVisualState(GroupName:="EditStates", Name:="ViewMode")> 
    Public Class EditableTabHeader 
        Inherits ContentControl 
        Private textBox As TextBox 
        Private previosLeftClickTime As DateTime = DateTime.Now 
        Private previosLeftClickPoint As Point 
        Private doubleClickSpan As TimeSpan = TimeSpan.FromSeconds(0.4) 
 
        Public Shared ReadOnly IsInEditModeProperty As DependencyProperty = DependencyProperty.Register("IsInEditMode", GetType(Boolean), GetType(EditableTabHeader), New PropertyMetadata(AddressOf OnIsInEditModeChanged)) 
 
        Public Sub New() 
            DefaultStyleKey = GetType(EditableTabHeader) 
        End Sub 
 
        Public Overloads Overrides Sub OnApplyTemplate() 
            MyBase.OnApplyTemplate() 
 
            Me.textBox = TryCast(Me.GetTemplateChild("TextBox"), TextBox) 
 
            AddHandler Me.textBox.LostFocus, AddressOf textBox_LostFocus 
        End Sub 
 
        Private Sub textBox_LostFocus(ByVal sender As Object, ByVal e As RoutedEventArgs) 
            Me.IsInEditMode = False 
        End Sub 
 
        Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs) 
            MyBase.OnMouseLeftButtonDown(e) 
 
            Dim currentTime = DateTime.Now 
            Dim currentPoint = e.GetPosition(Me) 
            Dim durationBetweenClicks = currentTime - previosLeftClickTime 
 
            If currentPoint = previosLeftClickPoint AndAlso durationBetweenClicks < Me.doubleClickSpan Then 
                e.Handled = True 
                Me.IsInEditMode = Not Me.IsInEditMode 
            End If 
 
            Me.previosLeftClickTime = DateTime.Now 
            Me.previosLeftClickPoint = e.GetPosition(Me) 
        End Sub 
 
        Public Property IsInEditMode() As Boolean 
            Get 
                Return CBool(GetValue(IsInEditModeProperty)) 
            End Get 
            Set(ByVal value As Boolean) 
                SetValue(IsInEditModeProperty, value) 
            End Set 
        End Property 
 
        Private Shared Sub OnIsInEditModeChanged(ByVal sender As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 
            Dim editableContentControl = TryCast(sender, EditableTabHeader) 
            Dim newValue = CBool(e.NewValue) 
 
            If Not newValue Then 
                editableContentControl.Content = editableContentControl.textBox.Text 
            End If 
 
            editableContentControl.ChangeVisualStates() 
        End Sub 
 
        Public Sub ChangeVisualStates() 
            If Me.IsInEditMode Then 
                VisualStateManager.GoToState(Me, "EditMode", True) 
            Else 
                VisualStateManager.GoToState(Me, "ViewMode", True) 
            End If 
        End Sub 
    End Class 

The major changes in the implementation of the EditableTabHeader control are:

  • The control contract definition is done using two attributes of type "TemplateVisualState" placed right above the class definition. With this contract we declare all of the existing states and parts for that control.

  • New dependency property IsInEditMode of Boolean type was added.

  • One text box field declaration plus three additional fields related to the editing. The text box field is initialized with the reference from the text box defined in the template when the base method OnApplyTemplate is invoked.

  • Call back method OnIsInEditModeChanged invoked every time the value of the dependency property IsInEditMode is changed. This method changes the state of the control depending on the value of the IsInEditMode property.

  • The method OnMouseLeftButtonDown was overridden to move the control from View to EditMode state and vice versa depending on some internal logic for edit.

Now you can add a RadTabControl to the MainPage.xaml and EditableTabHeader control to define the TabItems Header:

<UserControl x:Class="CSharp.RadTabControl.HowTo_EditableTabHeader.MainPage" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"    
    xmlns:example="clr-namespace:CSharp.RadTabControl.HowTo_EditableTabHeader" 
    Width="400" Height="300"> 
    <Grid x:Name="LayoutRoot" Background="White"> 
 
        <telerik:RadTabControl x:Name="radTabControl"> 
            <telerik:RadTabControl.ContentTemplate> 
                <!--The Content Template:--> 
                <DataTemplate> 
                    <Grid Background="WhiteSmoke"> 
                        <TextBlock Text="{Binding Content}" /> 
                    </Grid> 
                </DataTemplate> 
            </telerik:RadTabControl.ContentTemplate> 
            <telerik:RadTabControl.ItemTemplate> 
                <!--The Header Template:--> 
                <DataTemplate> 
                    <example:EditableTabHeader Content="{Binding Name, Mode=TwoWay}" /> 
                </DataTemplate> 
            </telerik:RadTabControl.ItemTemplate> 
        </telerik:RadTabControl> 
    </Grid> 
</UserControl> 

In the XAML code above we create new rad tab control and predefine its ItemTempalte and ItemContainerStyle. In the ItemContainerStyle definition we set the HeaderTemplate of the control to the EditableTabHeader control. EditableTabHeader control will use automatically the default template defined in the Themes\Generic.xaml file.

Open the appropriate MainPage.xaml code behind class and paste the following content to bind and populate the tab control to a collection of the custom object TabItemModel:

public partial class MainPage: UserControl 
{ 
    public MainPage() 
    { 
        InitializeComponent(); 
        radTabControl.ItemsSource = Enumerable.Range(1, 5).Select(num => 
            new TabItemModel() 
            { 
                Name = String.Format("Header {0}", num), 
                Content = String.Format("Content {0}", num) 
            }); 
    } 
} 
public class TabItemModel : ViewModelBase 
{ 
 private String name;  
 private String content; 
 public String Name 
 { 
  get 
  { 
   return this.name; 
  } 
  set 
  { 
   if (this.name != value) 
   { 
    this.name = value; 
    OnPropertyChanged("Name"); 
   } 
  } 
 } 
 
 public String Content 
 { 
  get 
  { 
   return this.content; 
  } 
  set 
  { 
   if (this.content != value) 
   { 
    this.content = value; 
    OnPropertyChanged("Content"); 
   } 
  } 
 } 
 
} 
Imports Telerik.Windows.Controls 
 
    Partial Public Class MainPage 
        Inherits UserControl 
 
        Public Sub New() 
            InitializeComponent() 
 
            radTabControl.ItemsSource = Enumerable.Range(1, 5).Select 
        End Sub 
    End Class 
    Public Class TabItemModel 
        Inherits ViewModelBase 
        Private _name As [String] 
        Private _content As [String] 
 
        Public Property Name() As [String] 
            Get 
                Return Me._name 
            End Get 
            Set(value As [String]) 
                If Me._name <> value Then 
                    Me._name = value 
                    OnPropertyChanged("Name") 
                End If 
            End Set 
        End Property 
 
        Public Property Content() As [String] 
            Get 
                Return Me._content 
            End Get 
            Set(value As [String]) 
                If Me._content <> value Then 
                    Me._content = value 
                    OnPropertyChanged("Content") 
                End If 
            End Set 
        End Property 
    End Class 

See Also

In this article