防止Prism创建新的导航视图

时间:2016-09-29 17:55:35

标签: c# .net wpf prism

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

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

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

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

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

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

修改

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

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

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

3 个答案:

答案 0 :(得分:0)

  

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

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

  

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

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

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

答案 1 :(得分:0)

我知道这是一个老问题。但我自己刚刚遇到过这个问题,如果其他人有这个问题,那么很可能会再次回来。

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

为了简化导航,我创建了一个内容如下的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;
}

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

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

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

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

答案 2 :(得分:0)

根据文件:

  

“对于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);