WinRT/UWP Frame and Page caching: How to create new page instance on Navigate() and keep the page instance on GoBack()

StackOverflow https://stackoverflow.com/questions/11539755

Pergunta

I'm trying to create an UWP (Universal Windows App) application with C#. My problem is the Frame control: If I use it without NavigationCacheMode = Required, every time the user goes back, the page is not kept in memory and will be recreated. If I set NavigationCacheMode to Required or Enabled, going back works correctly (no new page object) but if I navigate to another page from the same type, the previous page object is recycled and reused (no new page instance).

Desired behavior:

Is there a way to have the following behaviour with the original Frame control (like in Windows Phone):

  1. Create new page instance on Navigate()
  2. Keep the page instance on GoBack()

The only solution I know is to create an own Frame control but this leads to other problems (e.g.: missing SetNavigationState() method, etc...)

Sample scenario:

Simple application example with three pages: TvShowListPage, TvShowDetailsPage, SeasonDetailsPage.

  1. TvShowListPage is the entry page. After clicking on a TvShow navigate to TvShowDetailsPage.
  2. Now in TvShowDetailsPage select a season in the list and navigate to the TvShowDetailsPage.
  3. If navigating back, the pages should stay in memory to avoid reloading the pages.
  4. But if the users goes back to TvShowListPage and selects another TvShow the TvShowDetailsPage gets recycled and is maybe in the wrong state (eg showing the cast pivot instead of the first, seasons pivot)

I'm looking for the default Windows Phone 7 behavior: Navigating creates a new page on the page stack, going back removes the top page from the stack and displays the previous page from the stack (stored in the memory).

Solution:

Because there was no solution to this problem, I had to reimplement all paging relevant classes: Page, Frame, SuspensionManager, etc...

The library MyToolkit which provides all these classes can be downloaded here: https://github.com/MyToolkit/MyToolkit/wiki/Paging-Overview

References:

Foi útil?

Solução

Because there was no solution to this problem, I had to reimplement all paging relevant classes: Page, Frame, SuspensionManager, etc...

The solution can be downloaded here: https://github.com/MyToolkit/MyToolkit/wiki/Paging-Overview

Update:

The page class now also provides the OnNavigatingFromAsync method to show for example an async popup and cancel navigation if required...

Outras dicas

I had much the same problem. I wanted it such that when I was moving forward in Metro (Windows Store to be proper), it would create a new instance. However, when going back, it would keep the data that I wanted save.

So, I likewise used NavigationCacheMode = NavigationCacheMode.Enabled. I found that regardless of which way I was traversing, forward or backward, everything was always saved. So, I would go forward several pages, then step back a few. Hoping everything was reset as I moved forward, I invariably found that it wasn't; it had retained the data.

I tried everything, including writing my own back button code to include NavigationCacheMode = NavigationCacheMode.Disabled, but to no avail. As others have pointed out, once you have enabled it, the NavigationCacheMode simply won't disable.

I did find a solution. I went to the LayoutAwarePage.cs and simply made a minor change. Under the "OnNavigatedTo" I found the line:

// Returning to a cached page through navigation shouldn't trigger state loading
if (this._pageKey != null) return;

However, the comment ran contrary to what I wanted. I was looking for state loading in a unidirectional pattern. If moving forward, I wanted state loading; if moving backward I wanted the behavior the comment indicated - no state loading.

So I simply modified the line.

// Returning to a cached page through navigation shouldn't trigger state loading
if (this._pageKey != null && e.NavigationMode == NavigationMode.Back) return;

I have tested it and it works perfectly. Now, when navigating backwards it remembers the state and keeps the page the same. Navigating forward, it loads fresh.

Perhaps not best practice, but I do not call "OnNavigatedTo" from my code-behind. I do everything through the "LoadState." If you are overriding "OnNavigatedTo" in the code-behind you might see different behavior.

Thank you,

Joseph Irvine

When you are navigating forward, can you set NavigationCacheMode to Disabled before you call Frame.Navigate? Then, in OnNavigatedTo() set NavigationCacheMode back to Enabled again.

That should make it so that when you navigate forward, caching is disabled. But when you arrive on the new page instance, OnNavigatedTo would enable it again. When you want to navigate back, you wouldn't touch the NavigationCacheMode before calling Frame.GoBack. That should give you the cached instance, I think.

I believe this would work but I haven't tested it. I'd be curious to know if it does. Interesting scenario there. I'd love to see the app in action and better understand the use of this behavior.

You use the NavigationCacheMode property to specify whether a new instance of the page is created for each visit to the page or if a previously constructed instance of the page that has been saved in the cache is used for each visit.

The default value for the NavigationCacheMode property is Disabled. Set the NavigationCacheMode property to Enabled or Required when a new instance of the page is not essential for each visit. By using a cached instance of the page, you can improve the performance of your application and reduce the load on your server.

Setting NavigationCacheMode to Required means that the page is cached regardless of the number of cached pages specified in the CacheSize property. Pages marked as Required do not count against the CacheSize total. Setting NavigationCacheMode to Enabled means the page is cached, but it is eligible for disposal if the number of cached pages exceeds the value of CacheSize.

Set the NavigationCacheMode property to Disabled if a new instance must be created for each visit. For example, you should not cache a page that displays information that is unique to each customer.

The OnNavigatedTo method is called for each request, even when the page is retrieved from the cache. You should include in this method code that must be executed for each request rather than placing that code in the Page constructor.

I had to derive a page2 class from my page class and then when I want to navigate to a second version of the same page, I detect if the this object is page or page2. I then navigate to page2 if I was in page and navigate to page if in page2.

The only drawback, which is a huge one, is that there is no way to derive one XAML file from another. Thus, all of the C# code is in the page class code-behind as expected, but there are two almost identical XAML files, one for each version of the page.

A small script could probably be added as a pre-build step to generate the second page class from the first, copying the XAML data and adjusting the class names.

It's ugly but it almost works perfectly and I never have to worry about C# code duplication or weird navigation cache issues. I just end up having duplicate XMAL code, which in my case really never changes anyhow. I also end up with two warnings about not using the new keyword on the automatically generated code for page2.InitializeComponent() and page2.Connect().

Interestingly, navigating to page then to page2 then to page doesn't cause a problem and the second instance of the page class is an actual second instance unrelated to the first.

Note that this solution is probably recommended against by MS.

I achieved this by:

  • Setting NavigationCacheMode to Required/Enabled for required Pages.
  • On clicking on Page2 button/link:

    Traverse the Frame BackStack and find out if the Page2 is in BackStack. If Page2 is found call Frame.GoBack() required number of times. If not found just navigate to the new Page. This will work for any no of pages.

Code Sample:

public void Page2Clicked(object sender, RoutedEventArgs e)
{
int isPresent = 0;
int frameCount = 0;
//traverse BackStack in reverse order as the last element is latest page
for(int index= Frame.BackStack.Count-1; index>=0;index--)
{
    frameCount += 1;
    //lets say the first page name is page1 which is cached
    if ("Page2".Equals(Frame.BackStack[index].SourcePageType.Name))
    {
        isPresent = 1;
        //Go back required no of times
        while (frameCount >0)
        {
            Frame.GoBack();
            frameCount -= 1;
        }
        break;
    }

}
    if (isPresent == 0)
    {
    Frame.Content = null;
    Frame.Navigate(typeof(Page2));
    }
}

This will be helpful if forward/backward buttons will not be made much use of. as this solution will affect the forward/backward navigation. If you wish to use forward/backward navigation as well, then some additional cases has to be handled.

When NavigationCacheMode is enabled, you can call Dispose() (conditionally) in OnNavigatedFrom() to make sure that a new instance of the page will be created the next time the application navigates to it.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top