Some instructions on how to add/remove tab items within a WPF / MVVM setting.
Step 1: Create a new WPF application
Step 2: Add classes to implement ICommand
RelayCommand.cs
using System;
using System.Windows.Input;
namespace Tabs
{
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
// Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
// associated views should be enabled whenever a command is invoked
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
private event EventHandler CanExecuteChangedInternal;
public void RaiseCanExecuteChanged()
{
CanExecuteChangedInternal.Raise(this);
}
}
}
EventRaiser.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tabs
{
public static class EventRaiser
{
public static void Raise(this EventHandler handler, object sender)
{
handler?.Invoke(sender, EventArgs.Empty);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
{
handler?.Invoke(sender, new EventArgs<T>(value));
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
{
handler?.Invoke(sender, value);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
{
handler?.Invoke(sender, value);
}
}
}
EventArgs.cs
using System;
namespace Tabs
{
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}
Step 3: Add the ViewModel class
We need a class to implement the button click events, as well contain data for tab-related information.
MainWindowViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Linq;
namespace Tabs
{
public class MainWindowViewModel : INotifyPropertyChanged
{
static int tabs = 1;
public MainWindowViewModel()
{
Titles = new ObservableCollection<Item>();
}
public ObservableCollection<Item> Titles
{
get { return _titles; }
set
{
_titles = value;
OnPropertyChanged("Titles");
}
}
public class Item
{
public string Header { get; set; }
public string Content { get; set; }
}
private ICommand _addTab;
private ICommand _removeTab;
private ObservableCollection<Item> _titles;
public ICommand AddTab
{
get
{
return _addTab ?? (_addTab = new RelayCommand(
x =>
{
AddTabItem();
}));
}
}
public ICommand RemoveTab
{
get
{
return _removeTab ?? (_removeTab = new RelayCommand(
x =>
{
RemoveTabItem();
}));
}
}
private void RemoveTabItem()
{
Titles.Remove(Titles.Last());
tabs--;
}
private void AddTabItem()
{
var header = "Tab " + tabs;
var content = "Content " + tabs;
var item = new Item { Header = header, Content = content };
Titles.Add(item);
tabs++;
OnPropertyChanged("Titles");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[Conditional("DEBUG")]
private void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
throw new ArgumentNullException(GetType().Name + " does not contain property: " + propertyName);
}
}
}
Step 4: Create the XAML view
MainWindow.xaml
<Window x:Class="Tabs.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Tabs"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid
Grid.Column="0" Grid.Row="1">
<StackPanel>
<Button
Margin="0,0,0,10"
Content="Add"
Command="{Binding AddTab}"
Height="30" Width="80"
VerticalAlignment="Top" />
<Button
Content="Remove"
Command="{Binding RemoveTab}"
Height="30" Width="80"
VerticalAlignment="Top" />
</StackPanel>
</Grid>
<TabControl
TabStripPlacement="Top"
ItemsSource="{Binding Titles, Mode=TwoWay}"
Grid.Column="1" Grid.Row="1">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
So that when the code is run and the ‘Add’ button is pressed new tab items are added dynamically:
And that on pressing the ‘Remove’ button a few times tab item data is deleted from the list and the view is updated accordingly:


