0
votes

I use Prism 5.0, and I have trouble configuring it to reuse existing views. Whenever IRegionManager.RequestNavigate(string regionName, Uri source) is called, it creates a new view instead of using the view that was previously created. Strangely, CLRProfiler also indicates that Prism's region manager holds on to references to all the previously created view instances, leading to a memory leak.

My view models implement INavigationAware, and return true in IsNavigationTarget() but the method is never called. I tried implementing it on the view as well with the same result.

As a test, I implemented IActiveAware on the view, which shows that it is deactivated as soon as I navigate to another view (I'm not sure this is relevant).

I found this question: PRISM WPF - Navigation creates new view every time but my V-VM naming conventions match those of the answer (I use AutoFac, by the way).

I've only found a workaround: removing the active view from the region using NavigationContext.NavigationService.Region.Remove() in the view model's INavigationAware.OnNavigatedFrom() implementation. When I do this, Prism's region manager releases the reference to the view. This works but it seems inefficient to always recreate the view when it is needed.

Almost all the related questions on SO ask how to create a new view on a navigation event, so I assume the default behavior is that views are reused. I need pointers here.

EDIT

We use AutoFac's AutofacExtensions.RegisterTypeForNavigation<T>(this ContainerBuilder builder, string name = null) to register the views. We do use IRegionManager.RequestNavigate() for navigation between views. INavigationAware is implemented on the ViewModels. However, while INavigationAware.OnNavigatedTo() and OnNavigatedFrom() are called, IsNavigationTarget() is never called (the latter is not called even if the views implement INavigationAware).

I can detect that a new view is created by setting a breakpoint in the ctor of the view. A CLRProfiler heap dump also shows that the region manager has as many instances of the view as many times it has been navigated to. The ViewModels are only created once, as they are registered with AutoFac as single-instance.

As a temporary measure, we made the Views implement IRegionMemberLifetime, where KeepAlive returns false. This is not very effective, as the views are recreated every time they are needed but it prevents the region manager from holding on to previous views.

3
you need to use IRegionMemberLifetime and KeepAlive property. - Ayyappan Subramanian
@AyyappanSubramanian if the view implements IRegionMemberLifetime, and KeepAlive returns true, the region manager keeps a reference to the view but creates a new one when it is navigated to (this must be the default if the view doesn't implement IRML). If KeepAlive returns false, the view is disposed of, and so needs to be recreated. My question was how to make Prism reuse the original view, so this is not a solution. - Drew
I'm currently having the same issue although not on all of my views. It seems to be anything below a specific page which is causing me problems. - user3265613

3 Answers

1
votes

As per documentation:

"For Prism to determine the type of the target view, the view's name in the navigation URI should be the same as the actual target type's short type name. For example, if your view is implemented by the MyApp.Views.EmployeeDetailsView class, the view name specified in the navigation URI should be EmployeeDetailsView." https://prismlibrary.github.io/docs/wpf/Navigation.html

So in your module:

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

And to navigate to this view:

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

Whenever IRegionManager.RequestNavigate(string regionName, Uri source) is called, it creates a new view instead of using the view that was previously created

I cannot duplicate this behavior. When using the navigation framework, by default Prism reuses each view, regardless of if you are using the INavigationAware interface. This means it will keep the views in the region manager (not a memory leak, this is done on purpose). If you wish not to reuse views, you need to use the IRegionMemberLifetime interface or attribute and tell Prism not to reuse views. In this case the view will be removed from the region manager and a new one will be created.

My view models implement INavigationAware, and return true in IsNavigationTarget() but the method is never called. I tried implementing it on the view as well with the same result.

If this is the case, the you are not using RequestNavigate. You might be adding views to the region manually using IRegion.Add, or maybe your ViewModel isn't being set as the DataContext. If this is the case these methods will not be invoked.

How are you determining if a new view is being created? Are you setting a break point in the ctor of your Views and ViewModels? Are you properly registering your views for navigation?

0
votes

I know this is an old question. But I've just had this issue myself, and if other people have it, then it's likely to come back again some time.

The problem as I had it, was my page names.

To make navigation easier I created a PageNames class with content as follows.

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

and when registering pages and navigating my classes were somewhat like the following

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

Most of my pages had the same name as their class, IE MyPageView = MyPageView. However some pages were missing the View part of the pagename.

When PRISM checks if a page exists in a region it executes the following.

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

This is returning the page name. MyPageView. The request navigation content loader then calls this method to check if the page exists within the region.

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

which I believe (not sure how to directly debug PRISM) is checking whether the page name, matches the class name, or the full name including namespace. Because the page name isn't the same as the class name, it fails to find it and adds a new instance of that page to the region manager.

So in conclusion, the best fix is to make sure your pagename = class name.