I believe your MVVM is getting blured and you can do a lot more with binding.
I have put together an example of what I think you are after, using binding, templating and RoutedCommands to achieve the functionality you have said you are after.
It works like this...
There are 3 models in my example, MyModel1 to MyModel3 and they are all basically
public class MyModel1
{
public string Header { get { return "One"; }}
}
with the header returning a different value for each model.
The ViewModel simple as well
public class MyViewModel : INotifyPropertyChanged { private object selectedItem;
public MyViewModel()
{
this.AvailableItems = new Collection<Type>() { typeof(MyModel1), typeof(MyModel2), typeof(MyModel3) };
this.Items = new ObservableCollection<object>();
}
public Collection<Type> AvailableItems { get; set; }
public ObservableCollection<object> Items { get; set; }
public void AddItem(Type type)
{
var item = Items.FirstOrDefault(i => i.GetType() == type);
if (item == null)
{
item = Activator.CreateInstance(type);
Items.Add(item);
}
SelectedItem = item;
}
internal void RemoveItem(object item)
{
var itemIndex = this.Items.IndexOf(item);
if (itemIndex > 0)
{
SelectedItem = Items[itemIndex - 1];
}
else if (Items.Count > 1)
{
SelectedItem = Items[itemIndex + 1];
}
Items.Remove(item);
}
public object SelectedItem
{
get { return selectedItem; }
set
{
if (value != selectedItem)
{
selectedItem = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ListBox will bind to AvailableItems and the TabControl will bind to Items.
There are 3 UserControl's, one for each Model and they all look something like this
<UserControl x:Class="StackOverflow._20933056.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TextBlock Text="User Control 1" />
</UserControl>
The views code behind instantiates the ViewModel, registers a RoutedCommand and handles the events of the RoutedCommand.
public partial class MainWindow : Window
{
public static RoutedCommand CloseItemCommand = new RoutedCommand("CloseItem", typeof(MainWindow));
public MainWindow()
{
this.ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
private void MyListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.ViewModel.AddItem(e.AddedItems.OfType<Type>().FirstOrDefault());
}
private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.ViewModel.RemoveItem(e.Parameter);
}
More on the RoutedCommand later.
The fun is in the Xaml, which is quite simple
<Window x:Class="StackOverflow._20933056.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20933056"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="600" Width="800">
<Window.Resources>
<ContextMenu x:Key="TabContextMenu">
<MenuItem Header="Close" Command="{x:Static this:MainWindow.CloseItemCommand}" CommandParameter="{Binding}" />
</ContextMenu>
<DataTemplate DataType="{x:Type this:MyModel1}">
<this:UserControl1 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyModel2}">
<this:UserControl2 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyModel3}">
<this:UserControl2 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Path=Header}" />
<Setter Property="ContextMenu" Value="{StaticResource TabContextMenu}" />
</Style>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{x:Static this:MainWindow.CloseItemCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted" />
</Window.CommandBindings>
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox x:Name="MyListBox" ItemsSource="{Binding Path=AvailableItems}" SelectionChanged="MyListBox_OnSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type system:Type}"><TextBlock Text="{Binding Path=Name}" /></DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
</Grid>
</Grid>
</Window>
As I said earlier, the ListBox binds to the AvailableItems of the ViewModel and the TabControl binds to the Items. The TabControl also binds to the SelectedItem which allows control of the selected tab from the view model.
The ListBox.SelectionChanged event is handled in the code behind to call the ViewModel.AddItem method which adds or selects a tab item.
NOTE: Each tab in the TabControl is actually a Model object, not a TabItem control. There are DataTemplates defined to allow the TabControl to correctly insert the required UserControl for each Model in the content of the TabItem.
TabItem management is via the AddItem and RemoveItem methods in the ViewModel.
Now, back to the RoutedCommand.
The RoutedCommand allows a command to be defined, which can be fired from somewhere in the VisualTree and then pick it up somewhere else, with out the receiving handler caring about where it came from.
So, in the Xaml, there is a ContextMenu resource called TabContextMenu. That resource is bound to the ContextMenu of all the TabItem's via a Style. It is also bound the ContextMenu of each UserControl in the DataTemplates.
In the ContextMenu has a MenuItem that will fire the RoutedCommand, passing the current DataContext (the Model) with it.
The MainWindow has a CommandBinding that receives and handles the RoutedCommand. In the CommandBindings Executed event handler, the ViewModel.RemoveItem method is called.
The code here is almost the complete code base of my example. Only the implementation of MyModel2, MyModel3, UserControl2 and UserControl3 are missing from this answer and they can be inferred from MyModel1 and UserControl1. You should be able to reproduce the example in a new C#/WPF project.
I hope this helps.