MahApps.Metro icon indicating copy to clipboard operation
MahApps.Metro copied to clipboard

TransitioningContentControl recreates view content unnecessarily

Open Metritutus opened this issue 3 years ago • 2 comments

Describe the bug

There are two issues here but it's possible they have the same root cause as they seem rather similar, so I've included both of them in this case.

For demonstrating the problems I am following an MVVM approach. I have a MahApps TransitioningContentControl which switches between two different user controls depending on the selected view model.

Issue 1: When transitioning to a view model with a data template that is different to the one that is already displayed, the data template being unloaded has its view content recreated first.

Issue 2: When transitioning to a view model with the same data template as the one that is already displayed, the data template's view content is recreated anyway.

I'm highlighting these as bugs because this behaviour seems odd and has performance implications, not to mention the fact that the stock ContentControl does not behave in this way (I've included a commented-out ContentControl in my reproduction repo code for comparison purposes).

Steps to reproduce

Reproduction repo here: https://github.com/Metritutus/TransitioningContentControlTest001

The key parts of relevance here are the main view model, the three content view models and two user controls. MainViewModel holds the code for changing the content based on user actions. In MainWindow.xaml Content1ViewModel is associated with a data template that will load Content1UserControl. Content2ViewModel and Content3ViewModel are associated with two different data templates which will both load Content2UserControl.

I've set a Content1ViewModel instance as the initial content. Clicking the buttons will switch the content.

Issue 1 demonstration

Non-exhaustive examples:

If showing Content 1 and you click the CONTENT2 button, the Output Debug window will show the following messages:

Content1UserControl constructor.
Content2UserControl constructor.

If showing Content 1 and you click the CONTENT3 button, the Output Debug window will show the following messages:

Content1UserControl constructor.
Content2UserControl constructor.

If showing Content 2 and you click the CONTENT1 button, the Output Debug window will show the following messages:

Content2UserControl constructor.
Content1UserControl constructor.

If showing Content 2 and you click the CONTENT3 button, the Output Debug window will show the following messages:

Content2UserControl constructor.
Content2UserControl constructor.

Issue 2 demonstration

If showing Content 1 and you click the CONTENT1 button, the Output Debug window will show the following message:

Content1UserControl constructor.

If showing Content 2 and you click the CONTENT2 button, the Output Debug window will show the following message:

Content2UserControl constructor.

If showing Content 3 and you click the CONTENT3 button, the Output Debug window will show the following message:

Content2UserControl constructor.

Expected behavior

If the selected content view model is is changing and would result in a data template change, the view content of the data template being unloaded should not be getting recreated.

If the selected content view model is changing but the data template would not change as a result then the current view content should not be recreated.

For the examples shown in the Steps to reproduce section I would expect (matching the behaviour of the stock ContentControl) the following:

Issue 1 expected behaviour

If showing Content 1 and you click the CONTENT2 button, the Output Debug window should show the following messages:

Content2UserControl constructor.

If showing Content 1 and you click the CONTENT3 button, the Output Debug window should show the following messages:

Content2UserControl constructor.

If showing Content 2 and you click the CONTENT1 button, the Output Debug window should show the following messages:

Content1UserControl constructor.

If showing Content 2 and you click the CONTENT3 button, the Output Debug window should show the following messages:

Content2UserControl constructor.

Issue 2 expected behaviour

If showing Content 1 and you click the CONTENT1 button, the Output Debug window should show no messages.

If showing Content 2 and you click the CONTENT2 button, the Output Debug window should show no messages.

If showing Content 3 and you click the CONTENT3 button, the Output Debug window should show no messages.

Actual behavior

See the "Steps to reproduce" section.

Environment

MahApps.Metro version: v2.4.5
Windows build number: Win10 20H2 [Version 10.0.19042]
Visual Studio: 2019 16.9.4
Target Framework: .Net Core 3.1

Metritutus avatar May 22 '21 17:05 Metritutus

It's taken me a while to get here after noticing my views being created unexpectedly. Is there a plan to fix this?

I'm not sure how to fix it but the reason appears to be

this.previousContentPresentationSite.SetCurrentValue(ContentPresenter.ContentProperty, oldContent)

darbid avatar Aug 22 '23 12:08 darbid

Upon further research on this topic, and as I am not an expert, I wanted to add what I have found out to possibly help others. Firstly, I can imagine that it is possible that this is not seen as a bug. In fact it is seen as WPF working as expected and a decision of won't fix is possible. The problem exists because of the way the transitioning control has been implemented when a transition takes place, the old content is added to a control and the new content is added to a new control and then an animation is run to change what is visible, the problem with such an implementation is when you use a viewmodel first approach, meaning you have a datatemplate each time you add your data template to a control the view within the data template is created. One other thing to remember is that once the animation is completed and the Old content is removed if you are using a viewmodel your data context will be set to null and you will lose control of your view.

** changing the control** I have seen another transitioning control implementation, which would avoid this issue by not adding the old content to a new control, but by taking an image of the old content, showing the image, and then using an animation to animate from the image to the new content, meaning that there is no additional adding of content. I'm also wondering if the other alternative is to have a more flexible animation which doesn't require adding and removing content, just for the animation, in other words instead of having an old content presenter, and new content presenter, you just have two content presenters, and depending on which one is visible the animation runs to make the other one visible. In simple terms (and I'm sure it's not that simple ) this would mean a dynamic target name in the double animations of the current control.

As I am facing this problem, I have chosen to implement a cashing of views, which means that the datatemplate will get an existing view and only create a new one if it does not exist. A quick google search has a few examples of this.

here is one

I am also using this with the hamburger menu (I note that all of the examples carefully do not use the transition if there are data templates 🤫) therefore I wish to reuse the content during the life of the application. In this case you need to be careful about losing the data context and the fact that during that time you will have no control over your view. However when the data context comes back, it will re-query all of your bindings which also can cause problems if you have changed anything in your view model.

darbid avatar Aug 24 '23 05:08 darbid