As alluded to in @Tomas Levesque's answer to a duplicate of this question, the simplest thing that will work is to defer the binding of the values by adding a level of inditection via a ContentTemplate
DataTemplate
:-
<TabControl>
<TabItem Header="A" Content="{Binding A}">
<TabItem.ContentTemplate>
<DataTemplate>
<local:AView DataContext="{Binding Value}" />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
<TabItem Header="B" Content="{Binding B}">
<TabItem.ContentTemplate>
<DataTemplate>
<local:BView DataContext="{Binding Value}" />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
</TabControl>
Then the VM just needs to have some laziness:-
public class PageModel
{
public PageModel()
{
A = new Lazy<ModelA>(() => new ModelA());
B = new Lazy<ModelB>(() => new ModelB());
}
public Lazy<ModelA> A { get; private set; }
public Lazy<ModelB> B { get; private set; }
}
And you're done.
In my particular case, I had reason to avoid that particular Xaml arrangement and needed to be able to define my DataTemplate
s in the Resources
. This causes a problem as a DataTemplate
can only be x:Type
d and hence Lazy<ModelA>
can not be expressed via that (and custom markup annotations are explicitly forbidden in such definitions).
In that case, the most straightforward route around that is to define a minimal derived concrete type:-
public class PageModel
{
public PageModel()
{
A = new LazyModelA(() => new ModelA());
B = new LazyModelB(() => new ModelB());
}
public LazyModelA A { get; private set; }
public LazyModelB B { get; private set; }
}
Using a helper like so:
public class LazyModelA : Lazy<ModelA>
{
public LazyModelA(Func<ModelA> factory) : base(factory)
{
}
}
public class LazyModelB : Lazy<ModelB>
{
public LazyModelB(Func<ModelB> factory) : base(factory)
{
}
}
Which can then be consumed straightforwardly via DataTemplate
s:-
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:LazyModelA}">
<local:ViewA DataContext="{Binding Value}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:LazyModelB}">
<local:ViewB DataContext="{Binding Value}" />
</DataTemplate>
</UserControl.Resources>
<TabControl>
<TabItem Header="A" Content="{Binding A}"/>
<TabItem Header="B" Content="{Binding B}"/>
</TabControl>
One can make that approach more generic by introducing a loosely typed ViewModel:
public class LazyModel
{
public static LazyModel Create<T>(Lazy<T> inner)
{
return new LazyModel { _get = () => inner.Value };
}
Func<object> _get;
LazyModel(Func<object> get)
{
_get = get;
}
public object Value { get { return _get(); } }
}
This allows you to write more compact .NET code:
public class PageModel
{
public PageModel()
{
A = new Lazy<ModelA>(() => new ModelA());
B = new Lazy<ModelB>(() => new ModelB());
}
public Lazy<ModelA> A { get; private set; }
public Lazy<ModelB> B { get; private set; }
At the price of adding a sugaring/detyping layer:
// Ideal for sticking in a #region :)
public LazyModel AXaml { get { return LazyModel.Create(A); } }
public LazyModel BXaml { get { return LazyModel.Create(B); } }
And allows the Xaml to be:
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ModelB}">
<local:ViewB />
</DataTemplate>
<DataTemplate DataType="{x:Type local:LazyModel}">
<ContentPresenter Content="{Binding Value}" />
</DataTemplate>
</UserControl.Resources>
<TabControl>
<TabItem Header="A" Content="{Binding AXaml}" />
<TabItem Header="B" Content="{Binding BXaml}" />
</TabControl>