【问题标题】:WinRT/UWP Frame and Page caching: How to create new page instance on Navigate() and keep the page instance on GoBack()WinRT/UWP 帧和页面缓存:如何在 Navigate() 上创建新页面实例并将页面实例保留在 GoBack() 上
【发布时间】:2012-07-18 10:56:23
【问题描述】:

我正在尝试使用 C# 创建 UWP(通用 Windows 应用程序)应用程序。我的问题是Frame 控件:如果我在没有NavigationCacheMode = Required 的情况下使用它,则每次用户返回时,页面都不会保存在内存中,而是会重新创建。如果我将NavigationCacheMode 设置为RequiredEnabled,返回工作正常(没有新的页面对象)但是如果我从相同类型导航到另一个页面,前一个页面对象将被回收并重用(没有新的页面实例)。

期望的行为:

有没有办法使原始Frame 控件(如在 Windows Phone 中)具有以下行为:

  1. Navigate() 上创建新页面实例
  2. 将页面实例保持在GoBack()

我知道的唯一解决方案是创建一个自己的Frame 控件,但这会导致其他问题(例如:缺少SetNavigationState() 方法等...)

示例场景:

三个页面的简单应用示例:TvShowListPageTvShowDetailsPageSeasonDetailsPage

  1. TvShowListPage 是入口页面。单击TvShow 后导航到TvShowDetailsPage
  2. 现在在TvShowDetailsPage 的列表中选择一个季节并导航到TvShowDetailsPage
  3. 如果向后导航,页面应保留在内存中以避免重新加载页面。
  4. 但如果用户返回TvShowListPage 并选择另一个TvShow,则TvShowDetailsPage 会被回收并且可能处于错误状态(例如,显示演员轴心而不是第一个季节轴心)

我正在寻找默认的 Windows Phone 7 行为:导航在页面堆栈上创建一个新页面,返回从堆栈中删除顶部页面并显示堆栈中的前一页(存储在内存中)。

解决方案:

因为没有解决这个问题,我不得不重新实现所有与分页相关的类:Page、Frame、SuspensionManager 等...

提供所有这些类的库 MyToolkit 可以在这里下载:https://github.com/MyToolkit/MyToolkit/wiki/Paging-Overview

参考资料:

【问题讨论】:

  • 这可能是 Windows 8 上最烦人的事情之一。感谢您的解决方案!

标签: .net xaml windows-8 windows-runtime win-universal-app


【解决方案1】:

因为没有解决这个问题,我不得不重新实现所有与分页相关的类:Page、Frame、SuspensionManager 等...

解决方案可以在这里下载: https://github.com/MyToolkit/MyToolkit/wiki/Paging-Overview

更新:

页面类现在还提供 OnNavigatingFromAsync 方法来显示例如异步弹出窗口并在需要时取消导航...

【讨论】:

  • 我们是否必须将 Hamburger 项目与 HamburgerFrameBuilder 一起使用,因为我有自己的自定义汉堡包,反正我可以使用我的吗?像 frame.goback 和 frame.navigate 这样简单的东西,mtPage 作为参数和 args?
  • 不是强制的,查看GitHub页面上的文档或者联系我即可。
  • 好吧,想通了!我还有一个疑问 MtFrame.Navigate() 只接受一个参数,即;页面和任何参数都不能传递。如何向导航页面发送参数?
  • 无论如何,您应该始终使用异步变体,因为同步变体是“一劳永逸”。
【解决方案2】:

我也有同样的问题。我希望这样当我在 Metro(正确的是 Windows 商店)中前进时,它会创建一个新实例。但是,当返回时,它会保留我想要保存的数据。

所以,我同样使用 NavigationCacheMode = NavigationCacheMode.Enabled。我发现,无论我走哪条路,前进或后退,所有东西总是被保存下来。所以,我会前进几页,然后退后几页。希望在我前进的过程中一切都被重置,我总是发现它不是。它保留了数据。

我尝试了所有方法,包括编写自己的后退按钮代码以包含 NavigationCacheMode = NavigationCacheMode.Disabled,但无济于事。正如其他人指出的那样,一旦启用它,NavigationCacheMode 就不会禁用。

我确实找到了解决方案。我去了 LayoutAwarePage.cs 并简单地做了一个小改动。在“OnNavigatedTo”下,我找到了这一行:

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

但是,评论与我想要的相反。我正在寻找单向模式的状态加载。如果继续前进,我想要状态加载;如果向后移动,我想要评论指示的行为 - 没有状态加载。

所以我只是修改了这一行。

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

我已经对其进行了测试,并且效果很好。现在,当向后导航时,它会记住状态并保持页面不变。向前导航,它会重新加载。

也许不是最佳实践,但我不会从我的代码隐藏中调用“OnNavigatedTo”。我通过“LoadState”做所有事情。如果您在代码隐藏中覆盖“OnNavigatedTo”,您可能会看到不同的行为。

谢谢,

约瑟夫·欧文

【讨论】:

  • 这是否还会保留滚动位置等,还是仅保留您“手动保存”的状态?
  • UWP 应用如何实现这一点?
【解决方案3】:

在导航向前时,可以在调用Frame.Navigate之前将NavigationCacheMode设置为Disabled吗?然后,在 OnNavigatedTo() 中再次将 NavigationCacheMode 设置回 Enabled

这样应该可以在您向前导航时禁用缓存。但是当您到达新页面实例时,OnNavigatedTo 会再次启用它。当您想返回导航时,您不会在调用 Frame.GoBack 之前触摸 NavigationCacheMode。我认为这应该给你缓存的实例。

我相信这会起作用,但我还没有测试过。我很想知道它是否存在。有趣的场景。我很想看到该应用的实际运行情况并更好地了解此行为的用途。

【讨论】:

  • 它似乎不起作用。一些发现:NavigationCacheMode 必须在构造函数中设置才能生效。稍后更改属性(在导航离开时)没有任何效果...
  • 这对我有用,你从哪里读到它只有在构造函数中设置时才会生效?
  • @RicoSuter 好像以后UWP可以改NavigationCacheMode
【解决方案4】:

您使用 NavigationCacheMode 属性来指定是否为每次访问页面创建一个新的页面实例,或者是否每次访问都使用保存在缓存中的先前构造的页面实例。

NavigationCacheMode 属性的默认值为禁用。当页面的新实例对于每次访问都不是必需的时,请将 NavigationCacheMode 属性设置为 Enabled 或 Required。通过使用页面的缓存实例,您可以提高应用程序的性能并减少服务器上的负载。

将 NavigationCacheMode 设置为 Required 意味着无论 CacheSize 属性中指定的缓存页数如何,都会缓存页面。标记为必需的页面不计入 CacheSize 总数。将 NavigationCacheMode 设置为 Enabled 意味着页面被缓存,但如果缓存的页面数量超过 CacheSize 的值,则可以被丢弃。

如果必须为每次访问创建一个新实例,请将 NavigationCacheMode 属性设置为 Disabled。例如,您不应缓存显示每个客户独有信息的页面。

每个请求都会调用 OnNavigatedTo 方法,即使从缓存中检索页面也是如此。您应该在此方法中包含必须为每个请求执行的代码,而不是将该代码放在 Page 构造函数中。

【讨论】:

  • 一切正常,但这不是解决方案。您说唯一的方法是清理OnNavigatedTo() 中的页面(这不是一个好的解决方案,因为我的图像会先淡出等等......)?正如我在您的回答中看到的那样,我可以有行为 1. 或 2. 但不能两者兼有?
【解决方案5】:

我必须从我的page 类派生一个page2 类,然后当我想导航到同一页面的第二个版本时,我检测this 对象是page 还是page2 .然后,如果我在 page 中,我导航到 page2,如果在 page2 中,我导航到 page

唯一很大的缺点是无法从另一个 XAML 文件派生一个 XAML 文件。因此,正如预期的那样,所有 C# 代码都在 page 类代码隐藏中,但有两个几乎相同的 XAML 文件,一个用于页面的每个版本。

可能会添加一个小脚本作为预构建步骤,以从第一个页面类生成第二个页面类,复制 XAML 数据并调整类名。

它很丑,但几乎可以完美运行,我永远不必担心 C# 代码重复或奇怪的导航缓存问题。我最终得到了重复的 XMAL 代码,在我的情况下,它真的永远不会改变。最后我还收到了两个警告,即在为page2.InitializeComponent()page2.Connect() 自动生成的代码上不使用new 关键字。

有趣的是,导航到 page 然后到 page2 然后到 page 不会导致问题,page 类的第二个实例是与第一个无关的实际第二个实例。

请注意,MS 可能不建议使用此解决方案。

【讨论】:

  • 我刚刚发现这适用于面向 Windows 8.1 的应用程序,但不适用于 Windows 10 的 UWP 应用程序。在 UWP 应用程序中,无法在代码隐藏中访问第 2 页上的控件对于第 1 页。似乎有多个控件对象实例,每页一个。我无法解释它,但调试器显示了两个具有完全相同名称的成员变量。我可能是错的,因为我刚刚将 8.1 应用程序转换为 UWP 应用程序,但我仍然发现这样的小问题。可能仍有解决方案。
【解决方案6】:

我通过以下方式实现了这一目标:

  • 将所需页面的 NavigationCacheMode 设置为必需/启用。
  • 点击第2页按钮/链接:

    遍历Frame BackStack,看看Page2是否在BackStack中。 如果找到 Page2,则调用 Frame.GoBack() 所需的次数。 如果没有找到,只需导航到新页面。 这适用于任何页数。

代码示例:

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));
    }
}

如果不经常使用前进/后退按钮,这将很有帮助。 因为此解决方案将影响向前/向后导航。 如果您还希望使用前进/后退导航,则必须处理一些额外的情况。

【讨论】:

    【解决方案7】:

    启用 NavigationCacheMode 后,您可以在 OnNavigatedFrom() 中(有条件地)调用 Dispose() 以确保该页面将在应用程序下次导航到该页面时创建。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 1970-01-01
      • 2021-12-25
      • 1970-01-01
      • 2019-01-13
      • 1970-01-01
      相关资源
      最近更新 更多