I stumbled upon the famous TabControl
virtuality problem. I thought of replacing the TabControl
with styled list (to present tabs) and ContentControl
(to present content of a tab). However, it seems like ContentControl
has the same behavior of reusing DataTemplates
if two contents share their type.
Is there a way to force ContentControl
to instantiate separate DataTemplates
for all displayed items?
Edit: Example
Say, that we have the following UserControl acting as DataTemplate for document view model:
DocumentControl.xaml:
<UserControl x:Class="ControlTemplateProblem.DocumentControl"
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"
xmlns:local="clr-namespace:ControlTemplateProblem"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Loaded="UserControl_Loaded"
Unloaded="UserControl_Unloaded"
x:Name="Main">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Index}" />
<TextBlock Text="{Binding ElementName=Main, Path=StoredIndex}" />
</StackPanel>
</UserControl>
DocumentControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ControlTemplateProblem
{
/// <summary>
/// Interaction logic for DocumentControl.xaml
/// </summary>
public partial class DocumentControl : UserControl, INotifyPropertyChanged
{
private bool initialized = false;
private string storedIndex;
public DocumentControl()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
StoredIndex = ((ItemViewModel)DataContext).Index;
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
StoredIndex = "(detached)";
}
public string StoredIndex
{
get => storedIndex;
set
{
storedIndex = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StoredIndex)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Main window's view model looks like this:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ControlTemplateProblem
{
public class MainViewModel : INotifyPropertyChanged
{
private ItemViewModel selectedItem;
public MainViewModel()
{
selectedItem = Items.First();
}
public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel> {
new ItemViewModel(),
new ItemViewModel(),
new ItemViewModel()
};
public ItemViewModel SelectedItem
{
get => selectedItem;
set
{
selectedItem = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
MainWindow.xaml
<Window x:Class="ControlTemplateProblem.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:ControlTemplateProblem"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<ContentControl Content="{Binding SelectedItem}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<local:DocumentControl />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</StackPanel>
</Window>
I expect the document to display two equal indices - one coming directly from document's viewmodel and second extracted from the document's viewmodel by the control (this is simulation of performing some initializations on the control upon attaching viewmodel). After starting the application and choosing different items will yield always ( 0) and the 0 is because control was initialized only once and ContentControl reuses the DataTemplate.
Alternative question to the one I asked would be: how to reliably call code just after DataContext was set and just before DataContext is about to change? Then I'd be able to (again) reliaby initialize and deinitialize visuals for the specific viewmodel.