I would like to extend the tab control to have closeable tab items.
I have found this WPF solution of Kent: On the WPF TabControl - can I add content next to the tab
I had the same problem before, and then I decided to use an extended TabControl. I don't know where I've found it, but it doesn't matter, now it is in my project.
With this TabControl I can add or remove items from a collection of ViewModel and my changes will be reflected on user interface.
MyTabControl.cs
public class MyTabControl : TabControl
{
public MyTabControl()
: base()
{
this.SelectionChanged += OnSelectionChanged;
}
#region Tabs with databinding and templates
///
/// Template for a TabItem header
///
public DataTemplate TabHeaderItemTemplate
{
get { return (DataTemplate)GetValue(TabHeaderItemTemplateProperty); }
set { SetValue(TabHeaderItemTemplateProperty, value); }
}
public static readonly DependencyProperty TabHeaderItemTemplateProperty =
DependencyProperty.Register("TabHeaderItemTemplate", typeof(DataTemplate), typeof(MyTabControl), new PropertyMetadata(
(sender, e) =>
{
((MyTabControl)sender).InitTabs();
}));
///
/// Template for a content
///
public DataTemplate TabItemTemplate
{
get { return (DataTemplate)GetValue(TabItemTemplateProperty); }
set { SetValue(TabItemTemplateProperty, value); }
}
public static readonly DependencyProperty TabItemTemplateProperty =
DependencyProperty.Register("TabItemTemplate", typeof(DataTemplate), typeof(MyTabControl), new PropertyMetadata(
(sender, e) =>
{
((MyTabControl)sender).InitTabs();
}));
///
/// Source of clr-objects
///
public IEnumerable MyItemsSource
{
get
{
return (IEnumerable)GetValue(MyItemsSourceProperty);
}
set
{
SetValue(MyItemsSourceProperty, value);
}
}
public static readonly DependencyProperty MyItemsSourceProperty =
DependencyProperty.Register("MyItemsSource", typeof(IEnumerable), typeof(MyTabControl), new PropertyMetadata(
(sender, e) =>
{
MyTabControl control = (MyTabControl)sender;
INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= control.MyItemsSourceCollectionChanged;
}
control.InitTabs();
incc = e.NewValue as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += control.MyItemsSourceCollectionChanged;
}
}));
///
/// Selected item as object
///
public object MySelectedItem
{
get { return (object)GetValue(MySelectedItemProperty); }
set { SetValue(MySelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for MySelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MySelectedItemProperty =
DependencyProperty.Register("MySelectedItem", typeof(object), typeof(MyTabControl), new PropertyMetadata(
(sender, e) =>
{
MyTabControl control = (MyTabControl)sender;
if (e.NewValue == null)
control.SelectedItem = null;
else
{
var tab = control.Items.Cast().FirstOrDefault(ti => ti.DataContext == e.NewValue);
if (tab != null && control.SelectedItem != tab)
control.SelectedItem = tab;
}
}));
private void InitTabs()
{
Items.Clear();
if (MyItemsSource != null && MyItemsSource.OfType
MainPage.xaml
Notice, that the properties are called MyItemsSource and MySelectedItem, because this TabControl use objects, not TabItem.
And two ViewModels: MainViewModel.cs
public class MainViewModel
{
public MainViewModel()
{
this.Items = new ObservableCollection
{
new TabItemViewModel("Tab 1", OnItemRequestClose),
new TabItemViewModel("Tab item 2", OnItemRequestClose)
};
}
public ObservableCollection Items { get; set; }
public void OnItemRequestClose(TabItemViewModel item)
{
this.Items.Remove(item);
}
}
TabItemViewModel.cs
public class TabItemViewModel
{
public TabItemViewModel(string title, Action onClose)
{
this.Title = title;
this.RequestCloseCommand = new DelegateCommand(_ => onClose(this));
//Just a demontration
this.Content = "Test content "+title;
}
public string Title { get; set; }
public ICommand RequestCloseCommand { get; set; }
public object Content { get; set; }
}