4

I have two DataTemplates that gets switched depending on the current ViewModel. However whenever I switch my ViewModel, it seems to call the respective View's constructor and calls the InitializeComponent() call within the constructor, which means that whenever I switch the DataTemplate, it generates a new view that is bound to the respective DataTemplate. I am not sure why this is happening but is there a way to prevent the creation of a new View when switching ViewModels?

Below is the DataTemplates located at my MainView.

<Window.Resources>
    <DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
        <views:FirstPanelView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
        <views:SecondPanelView />
    </DataTemplate>
</Window.Resources>

The template is being displayed in a ContentControl.

<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />    

This is my SecondPanelView which is the same as my FirstPanelView, it's very simple.

public partial class FirstPanelView
{
    public FirstPanelView()
    {
        InitializeComponent();
    }
}
public partial class SecondPanelView
{
    public SecondPanelView()
    {
        InitializeComponent();
    }
}

My Ioc makes sure that I generate only one instance of the SecondPanelView

container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, SecondPanelViewModel>(new PerContainerLifetime());

DataContext is being bounded in each view by a custom markup extension.

DataContext="{Binding Source={common:Locate}, Path=FirstPanelViewModel}"
DataContext="{Binding Source={common:Locate}, Path=SecondPanelViewModel}"

Which is just calling GetInstance of the respective ViewModel.

public IFirstViewModel FirstViewModel
{
    get { return _container.GetInstance<IFirstPanelViewModel>(); }
}
public ISecondViewModel SecondViewModel
{
    get { return _container.GetInstance<ISecondPanelViewModel>(); }
}
Mike
  • 826
  • 11
  • 31
  • 3
    This is by design in WPF. If a view goes out of scope the view is destroyed and must be recreated if it needs to be shown again. This happens if you use ViewModel first approach, a.k.a. DataTemplates. Same thing happens using a TabControl with binded source items and data templates. Even though the ViewModel is not recreated, the view is. The only way to get around this is make have control over how the view is created. This can be done in a custom control. See [here](http://stackoverflow.com/questions/3877611/is-it-possible-to-cache-the-view-when-using-model-first-approach). – Michael Jul 19 '16 at 21:33
  • You could try [this](http://stackoverflow.com/questions/9794151/stop-tabcontrol-from-recreating-its-children), but by extending a ContentControl instead. – Michael Jul 19 '16 at 21:37
  • Thanks for that suggestion. I'll give this method a go. – Mike Jul 19 '16 at 21:49
  • Your question seems to involve some infrastructure beyond the basic WPF API. What is `container`? What API specifically are you using? In WPF proper, the answer to your question would be simply that you would need to ensure both views remain instantiated, i.e. keep both view model objects around, and just hide/show the appropriate one (e.g. bind a flag from each view model to the view's visibility that represents which one is active). But it's possible whatever other API you're using can do this more elegantly. – Peter Duniho Jul 19 '16 at 21:50
  • Of course, there remains the question of "why do you care?" I.e. is it really so bad for the view to be recreated on demand? – Peter Duniho Jul 19 '16 at 21:50
  • The container is just part of the dependency injection framework I am using. It is so that my view models can be abstracted. The reason I want the view to not be recreated is that the view itself is a container for a threaded video player. It is not desirable to create a new video player instance. I have avoided disposing the view because I wanted the view to be static (GC does not pick it up immediately). I want to try what Michael suggested, but I think the simplest solution that I can think of right now is to control visibility of my panels instead of using a DataTemplate. – Mike Jul 19 '16 at 21:59

2 Answers2

6

This is an old issue, but I was also struggling with this issue. The answer is to place the view instances directly in the resources and bind them to content controls in the data templates. If you do so, the view is instantiated only once.

<Window.Resources>
    <views:FirstPanelView x:Key="FirstPanelViewKey"/>
    <views:SecondPanelView x:Key="SecondPanelViewKey"/>
    <DataTemplate x:Key="DT1">
        <ContentControl Content="{StaticResource FirstPanelViewKey}" />
    </DataTemplate>
    <DataTemplate x:Key="DT2">
        <ContentControl Content="{StaticResource SecondPanelViewKey}" />
    </DataTemplate>
</Window.Resources>
Jörg
  • 140
  • 1
  • 8
  • What if you have a List and you want a different view for each item? So the UI will only show the active one. – JP Garza Mar 18 '21 at 19:05
0

I wasn't able to solve my problem even after extending ContentControl. The issue I ran into using that approach is that ContentControl's dependency property was not directly interfacable/overridable which forced me to hack on the existing dependency property. Also the intialization of a DataTemplate seems to fall deeper than the simple ContentControl.

So I decided to change the way that my views are being displayed by simply toggling their visibility. This approach worked for me since I essentially want my views to stay in the background doing its own thing and ready to be interfaced at its previous state in any moment.

Mike
  • 826
  • 11
  • 31