【问题标题】:prevent Prism from creating new view on navigation防止 Prism 在导航上创建新视图
【发布时间】:2016-09-29 17:55:35
【问题描述】:

我使用 Prism 5.0,但在配置它以重用现有视图时遇到了麻烦。每当调用IRegionManager.RequestNavigate(string regionName, Uri source) 时,它都会创建一个新视图,而不是使用之前创建的视图。奇怪的是,CLRProfiler 还表明 Prism 的区域管理器保留对所有先前创建的视图实例的引用,从而导致内存泄漏。

我的视图模型实现INavigationAware,并在IsNavigationTarget() 中返回true,但该方法从未被调用。我也尝试在视图上实现它,结果相同。

作为测试,我在视图上实现了IActiveAware,这表明当我导航到另一个视图时它被停用(我不确定这是否相关)。

我发现了这个问题:PRISM WPF - Navigation creates new view every time,但我的 V-VM 命名约定与答案匹配(顺便说一下,我使用 AutoFac)。

我只找到了一种解决方法:在视图模型的 INavigationAware.OnNavigatedFrom() 实现中使用 NavigationContext.NavigationService.Region.Remove() 从区域中删除活动视图。当我这样做时,Prism 的区域经理会释放对视图的引用。这可行,但总是在需要时重新创建视图似乎效率低下。

几乎所有关于 SO 的相关问题都询问如何在导航事件上创建新视图,因此我假设默认行为是重用视图。我这里需要指点。

编辑

我们使用 AutoFac 的 AutofacExtensions.RegisterTypeForNavigation<T>(this ContainerBuilder builder, string name = null) 来注册视图。我们确实使用IRegionManager.RequestNavigate() 在视图之间导航。 INavigationAware 在 ViewModel 上实现。然而,虽然INavigationAware.OnNavigatedTo()OnNavigatedFrom() 被调用,但IsNavigationTarget() 永远不会被调用(即使视图实现了INavigationAware,后者也不会被调用)。

我可以通过在视图的 ctor 中设置断点来检测是否创建了新视图。 CLRProfiler 堆转储还显示,区域管理器具有与它被导航到的次数一样多的视图实例。 ViewModel 只创建一次,因为它们在 AutoFac 中注册为单实例。

作为临时措施,我们让视图实现了IRegionMemberLifetime,其中KeepAlive 返回false。这不是很有效,因为每次需要时都会重新创建视图,但它会阻止区域管理器保留以前的视图。

【问题讨论】:

  • 你需要使用 IRegionMemberLifetime 和 KeepAlive 属性。
  • @AyyappanSubramanian 如果视图实现了 IRegionMemberLifetime,并且 KeepAlive 返回 true,则区域管理器保留对视图的引用,但在导航到时创建一个新的引用(如果视图没有,则必须是默认值'不实施 IRML)。如果 KeepAlive 返回 false,则视图被丢弃,因此需要重新创建。我的问题是如何让 Prism 重用原始视图,所以这不是解决方案。
  • 我目前也遇到了同样的问题,虽然不是我所有的观点。似乎是特定页面下方的任何内容都导致了我的问题。

标签: c# .net wpf prism


【解决方案1】:

根据文档:

"为了让Prism确定目标视图的类型,视图的名称 在导航 URI 中应与实际目标类型的相同 短类型名称。例如,如果您的视图由 MyApp.Views.EmployeeDetailsView 类,视图名称在 导航 URI 应该是 EmployeeDetailsView。” https://prismlibrary.github.io/docs/wpf/Navigation.html

所以在你的模块中:

_container.RegisterTypeForNavigation<Views.MyView>(nameof(Views.MyView));

然后导航到这个视图:

Uri uri = new Uri(nameof(Views.MyView), UriKind.Relative);
this.RegionManager.RequestNavigate("ContentRegion", uri);

【讨论】:

    【解决方案2】:

    每当调用 IRegionManager.RequestNavigate(string regionName, Uri source) 时,它都会创建一个新视图,而不是使用之前创建的视图

    我无法复制这种行为。使用导航框架时,默认情况下 Prism 会重用每个视图,无论您是否使用 INavigationAware 界面。这意味着它将保留区域管理器中的视图(不是内存泄漏,这是故意的)。如果您不想重用视图,则需要使用 IRegionMemberLifetime 接口或属性并告诉 Prism 不要重用视图。在这种情况下,视图将从区域管理器中移除,并创建一个新视图。

    我的视图模型实现了 INavigationAware,并在 IsNavigationTarget() 中返回 true,但从未调用过该方法。我也尝试在视图上实现它,结果相同。

    如果是这种情况,您没有使用 RequestNavigate。您可能正在使用 IRegion.Add 手动将视图添加到区域,或者您的 ViewModel 未设置为 DataContext。如果是这种情况,这些方法将不会被调用。

    您如何确定是否正在创建新视图?您是否在视图和视图模型的 ctor 中设置断点?您是否正确注册了导航视图?

    【讨论】:

    • 感谢您的回答。我编辑了帖子以澄清一些事情。
    • 我讨厌复活旧问题,但我遇到了完全相同的问题。但是,它只会影响某些视图。有没有办法在运行我的主应用程序时调试棱镜源?
    • @user3265613 我建议你从 Prismlibrary Github 页面获取源代码并引用项目文件而不是 NuGet 包。 github.com/PrismLibrary/Prism/tree/master/Source/Wpf
    【解决方案3】:

    我知道这是一个老问题。但是我自己也遇到了这个问题,如果其他人也有,那么它可能会在一段时间内再次出现。

    我遇到的问题是我的页面名称。

    为了使导航更容易,我创建了一个 PageNames 类,其内容如下。

    public static class PageNames
    {
        public const string MyPage= "MyPageView";
    }
    

    在注册页面和导航我的课程时有点像下面

    Kernel.RegisterTypeForNavigation<MyPageView>( PageNames.MyPage );
    

    我的大多数页面都与它们的类同名,IE MyPageView = MyPageView。但是,某些页面缺少页面名称的 View 部分。

    当 PRISM 检查某个页面是否存在于某个区域中时,它会执行以下操作。

    protected virtual string GetContractFromNavigationContext(NavigationContext navigationContext)
    {
        if (navigationContext == null) throw new ArgumentNullException(nameof(navigationContext));
         var candidateTargetContract = UriParsingHelper.GetAbsolutePath(navigationContext.Uri);
        candidateTargetContract = candidateTargetContract.TrimStart('/');
        return candidateTargetContract;
    }
    

    这是返回页面名称。我的页面视图。请求导航内容加载器然后调用此方法来检查页面是否存在于区域内。

    return region.Views.Where(v =>
       string.Equals(v.GetType().Name, candidateNavigationContract, 
    StringComparison.Ordinal) ||
        string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
    

    我相信(不确定如何直接调试 PRISM)正在检查页面名称、类名或全名是否匹配,包括命名空间。由于页面名称与类名称不同,因此无法找到它并将该页面的新实例添加到区域管理器。

    因此,总而言之,最好的解决方法是确保您的 pagename = 类名。

    【讨论】:

      猜你喜欢
      • 2011-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多