Introduction
Recently, I needed to develop a Tab Control in WPF where the Tab Headers become editable while user double clicks on that. The main idea is to provide a ControlTemplate
for the Tab Header and associate necessary Triggers on that.
Using the Code
At first, we need to design the ControlTemplate
for the Tab Header. The Tab Header is simply a TextBlock
, double clicking on it will turn it into a TextBox
. So, the XAML required for this is very simple:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:EditableTabHeaderControl}">
<Grid>
<TextBox x:Name="PART_TabHeader" Text="{Binding RelativeSource=
{RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"
Visibility="Collapsed"/>
<TextBlock x:Name="PART_TextBlock" Text="{Binding RelativeSource=
{RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsInEditMode" Value="True">
<Trigger.Setters>
<Setter TargetName="PART_TabHeader" Property="Visibility"
Value="Visible"/>
<Setter TargetName="PART_TextBlock" Property="Visibility"
Value="Collapsed"/>
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Now, we need to provide the class "EditableTabHeaderControl
", which will have the dependency properties and logic to control the visibility of textbox
. textblock
pair. Here is the class:
namespace EditableTabHeaderDemo
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
[TemplatePart(Name = "PART_TabHeader", Type = typeof(TextBox))]
public class EditableTabHeaderControl : ContentControl
{
private static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register("IsInEditMode", typeof(bool),
typeof(EditableTabHeaderControl));
private TextBox textBox;
private string oldText;
private DispatcherTimer timer;
private delegate void FocusTextBox();
public bool IsInEditMode
{
get
{
return (bool)this.GetValue(IsInEditModeProperty);
}
set
{
if (string.IsNullOrEmpty(this.textBox.Text))
{
this.textBox.Text = this.oldText;
}
this.oldText = this.textBox.Text;
this.SetValue(IsInEditModeProperty, value);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.textBox = this.Template.FindName("PART_TabHeader", this) as TextBox;
if (this.textBox != null)
{
this.timer = new DispatcherTimer();
this.timer.Tick += TimerTick;
this.timer.Interval = TimeSpan.FromMilliseconds(1);
this.LostFocus += TextBoxLostFocus;
this.textBox.KeyDown += TextBoxKeyDown;
this.MouseDoubleClick += EditableTabHeaderControlMouseDoubleClick;
}
}
public void SetEditMode(bool value)
{
this.IsInEditMode = value;
this.timer.Start();
}
private void TimerTick(object sender, EventArgs e)
{
this.timer.Stop();
this.MoveTextBoxInFocus();
}
private void MoveTextBoxInFocus()
{
if (this.textBox.CheckAccess())
{
if (!string.IsNullOrEmpty(this.textBox.Text))
{
this.textBox.CaretIndex = 0;
this.textBox.Focus();
}
}
else
{
this.textBox.Dispatcher.BeginInvoke
(DispatcherPriority.Render, new FocusTextBox(this.MoveTextBoxInFocus));
}
}
private void TextBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
this.textBox.Text = oldText;
this.IsInEditMode = false;
}
else if (e.Key == Key.Enter)
{
this.IsInEditMode = false;
}
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
this.IsInEditMode = false;
}
private void EditableTabHeaderControlMouseDoubleClick
(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
this.SetEditMode(true);
}
}
}
}
One problem arises when the control become editable. That is, when the visibility of the textbox
turns to visible from collapsed, it does not get focus automatically. To achieve that, I have added a timer which fires after 1 millisecond of the textbox
became editable and brings it to focus. Now, we are ready to use our Editable
Header Template. We will add a WPF TabControl
and provide ItemContainerStyle
to the control. Then, when we double click the Header, it will be editable.
<Window x:Class="EditableTabHeaderDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EditableTabHeaderDemo"
Title="EditableTabHeaderDemo" Height="300" Width="500">
<Window.Resources>
<Style x:Key="EditableTabHeaderControl"
TargetType="{x:Type local:EditableTabHeaderControl}">
</Style>
<Style x:Key="ItemContainerStyle" TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<local:EditableTabHeaderControl
Style="{StaticResource EditableTabHeaderControl}">
<local:EditableTabHeaderControl.Content>
<Binding Path="Name" Mode="TwoWay"/>
</local:EditableTabHeaderControl.Content>
</local:EditableTabHeaderControl>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ContentTemplate">
<Grid>
<TextBlock HorizontalAlignment="Left" Text="{Binding Name}"/>
<TextBlock HorizontalAlignment="Center" Text="{Binding City}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl Grid.Row="0" ItemsSource="{Binding Data}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
ContentTemplate="{StaticResource ContentTemplate}" />
</Grid>
</Window>
History
- 20th December, 2010: Initial post