I have a TabControl showing views from ObservableCollection.
ProjectViewModel and ProjectVarioViewModel do implement IProjectViewModel. The first of each get one instance of its view, but the next ones of the same type will get the same view instance as the first, which is problematic since the first view then won't match the subsequent viewmodels.
How to get one view instance for each view model ?
Each project will customize the View extensively, and it does not make sense to destroy/rebuild the view every time I switch from project to another.
I remarked that without ItemContainerStyle, tabitem header and body will be set with a view, the header will get its unique instance, but the body will not, ie., the same view is used, which is problematic.
So, more precisely: why do I get only one instance of view for the viewmodels of the same type in the body, and one instance of view per viewmodel in the header ? And how to get one instance of view per viewmodel in the body ?
Repro: in the following implementation, each View instance will have its own UniqueID, to verify that each view has its own instance or not. To test this visually, create at least 2 projects of the same type, and verify that UniqueID of the view is the same.
Here I have VarioViewModels with different configurations (W,H), and because the View is of the same instance, I do not have the proper Grid configuration.
The core of the problem:
When I remove completely TabControl.ItemContainerStyle, I get 1 view instance per tabitem header, but only 1 view instance in in the content of the tabitem, as show in the picture below:
When I select a tabitem by clicking on the header, I get the correct change of viewmodel, but the view instance is confirmed to be the same for all viewmodel instance of the same type.
A full window code: MainWindow.xaml:
View models:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MultiTab;
public interface IProjectViewModel : ICloneable
{
public string ProjectTitle { get; set; }
public float Margin { get; set; }
public int W { get; }
public int H { get; }
}
public class Project : IProjectViewModel
{
public Project(string name) { ProjectTitle = name; }
public float Margin { get; set; }
public string ProjectTitle { get; set; }
public int W { get; protected set; }
public int H { get; protected set; }
public object Clone() => MemberwiseClone();
public override string ToString() => $"{ProjectTitle} {W}x{H}";
}
public class ProjectViewModel : Project
{
public ProjectViewModel(string name) : base(name)
{
W = 2;
H = 2;
}
}
public class ProjectVarioViewModel : Project
{
private Random _r = new();
public ProjectVarioViewModel(string name) : base(name)
{
W = _r.Next(2, 16);
H = _r.Next(2, 16);
}
}
public enum ProjectType
{
Triptych,
Vario,
}
public class MainViewModel : INotifyPropertyChanged
{
private IProjectViewModel? _selectedProject;
private DelegateCommandListen? _newProjectCommand;
private ICommand? _closeProjectCommand;
private Dictionary
Now views, PView and VarioView.
PView:
PView code behind:
using System.Windows.Controls;
using System.Xml;
namespace MultiTab
{
///
/// Interaction logic for TView.xaml
///
public partial class TView : UserControl
{
public TView()
{
InitializeComponent();
}
public UniqueId NameID { get; } = new();
}
}
VarioView:
x
VarioView codebehind:
public partial class VarioView : UserControl
{
private static Random _r = new ();
private SolidColorBrush GetRnColor()
{
var rgb = new byte[3];
_r.NextBytes(rgb);
return new SolidColorBrush(Color.FromRgb(rgb[0], rgb[1], rgb[2]));
}
public ProjectVarioViewModel ViewModel
{
get => (ProjectVarioViewModel)DataContext;
set => DataContext = value;
}
public UniqueId NameID { get; } = new();
public VarioView()
{
InitializeComponent();
Loaded += ApplyGrid;
}
private void ApplyGrid(object sender, RoutedEventArgs e)
{
var totCols = ViewModel.W;
var totRows = ViewModel.H;
var zoneWLen = new GridLength(1, GridUnitType.Star);
for (int x = 0; x < totCols; x++)
GridZonesContainer.ColumnDefinitions.Add(new ColumnDefinition { Width = zoneWLen });
for (int y = 0; y < totRows; y++)
GridZonesContainer.RowDefinitions.Add(new RowDefinition { Height = zoneWLen });
for (int x = 0; x < totCols; x++)
for (int y = 0; y < totRows; y++)
AddZone(x, y);
}
private void AddZone(int gridx, int gridy)
{
var mz = new Rectangle
{
Fill = GetRnColor()
};
GridZonesContainer.Children.Add(mz);
Grid.SetRow(mz, gridy);
Grid.SetColumn(mz, gridx);
}
}
The utility DelegateCommandListen:
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows.Input;
namespace MultiTab;
///
/// Implementation of ICommand with listening to 1+ properties change (INPC)
/// ICommand Zcommand = new DelegateCommandListen {
/// ExecuteDelegate = ZExecuteCommand,
/// CanExecuteDelegate = CanExecuteZCommand }.ListenOn(this, o => o.INPCpropFromVM);;
///
public class DelegateCommandListen : ICommand
{
private readonly List _controlEvent;
private Action
Git repo: MultiTab
