View Navigation breaks with IRegionMemberLifetime and INavigationAware

Topics: Prism v4 - WPF 4
Aug 13, 2013 at 5:27 PM
I am trying to navigate between 2 views in 2 different modules within one region in my shell.

One view is essentially a main menu/home screen (additional features and modules will be added in the future) and the other view is the primary use case for the application (data entry).

Right now I register each view in the Module Definition with my Unity container.
container.RegisterType<Object, F5472View>(ViewNames.F5472View);
container.RegisterType<Object, MainMenuView>(ViewNames.MainMenuView);
And I use RequestNavigate to change views
regionManager.RequestNavigate(RegionNames.ContentRegion, ViewNames.F5472View);
regionManager.RequestNavigate(RegionNames.ContentRegion, ViewNames.MainMenuView);
My ViewModels for each View currently implement IRegionMemberLifetime and set KeepAlive to true, so I only ever have 1 instance of each view and viewmodel. However I would like to implement INavigationAware so I can some initilization and handle confirmation and saving process before I navigate to or from a view.

When I implement the interface my navigation breaks. I still RequestNavigate in the same manner but the navigation no longer happens. I believe the issue comes from IsNavigationTarget but I cannot figure out how to use it properly?

Can both these interfaces be implemented and used together? Or should only one of them be used? Do these interfaces play nicely with the TransientLifetimeManager and/or ContainerControlledLifetimeManager? Before using the IRegionMemberLifetime I tried using the ContainerControlled and the same issue happened, navigation would fail (no error) despite using the same approach and what seems to be the approach in most tutorials.

Thanks for any help and insights!
Developer
Aug 13, 2013 at 7:06 PM
Hi,

As far as I know, you should be able to implement both IRegionMemberLifetime and INavigationAware interfaces in your view models without problems.
So far with the information you provided I am not being able to find what is the cause behind the problem you are experiencing; but as a starting point you could check the following:
  • Is your "ContentRegion" an ItemsControl? Is so, try changing it to a ContentControl and check if this behavior keeps appearing.
  • In the IsNavigationTarget of your view models try changing between returning true and false to check if this behavior changes depending on that value.
If there is no new clues after checking those points, then it could be useful if you could provide us with your sample so that we can analyze what is causing the problem in deep.

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Aug 13, 2013 at 8:52 PM
Thank you for the reply. I am using a ContentControl and I double checked so that was not an issue, but I have been doing what you suggested in trying different values true/false with IsNavigationTarget and KeepAlive values. By doing this (and putting MessageBox's inside the INavigationAware methods, I was able to better see when those methods were being fired).

Most interesting is that IsNavigationTarget does not get called on the very first NavigationRequest to the view. Subsequent navigations back and forth do call IsNavigationTarget. I am not sure why but by trying different values and watching the execution I have been able to get the navigation to work they way I would like. This leads to a small follow up question, its not a technical problem as much as a question if this is what the Prism usage was meant to support.

I am not using the ContainerControlledLifetimeManager, but rather the TransientLifetimeManager with my Views from this project. However I do want only one instance of the view/viewmodel to be resolved. I am doing this now by using the IRegionMemberLifetime to keep the view in the region, and that same view is being activated if I ever return to it, instead of resolving a new instance (like the TransientLifetimeManager typically does). Is this an appropriate usage of IRegionMemberLifetime or is it more of a hack and am I missing out on benefits of using the proper LifetimeManager?

My biggest reason for trying to avoid the ContainerControlledManager is for future use cases that will have modules with multiple views (this current module has 1 view). In essence every time a user selects an option from the main menu a new instance of the chosen view should be activated, but if that view is split into (3 views for example) the user should be able to go between those 3 views always using the same instances. Once the user returns to the main menu and a new action chosen, a new view should be resolved. It seems that the ContainerControlledLifetimeManager would not allow for that use case.
Developer
Aug 14, 2013 at 9:40 PM
Hi,

The first time you navigate to a view its IsNavigationTarget method will not be checked, mainly because Prism doesn't need to use it at that point. The IsNavigationTarget method is used to check if an instance of the required view that is deactivated inside the region is the target of the navigation request, according to the parameters passed, etc. If no view of that type in the region return true in their IsNavigationTarget method, then Prism will instead create a new instance of said view. At this point, there is no reason to check the IsNavigationTarget of the new view, as it has been explicitly created to be the navigation target.

When navigating using regions you will find that a view can be active (it is being shown in the region,) deactivated (it is not being shown, but is still kept alive by the region) or removed from the region (the region no longer has a reference to the view). The function of the IRegionMemberLifetime is to specify if a view should also be removed from the region the moment it's deactivated, or should it be kept deactivated in the region so that it could be reused later. However, this is different from the lifetime you can set in the container.

First of all, the effect of the IRegionMemberLifetime is limited to the region containing the view only and does not have any effect on the instances of different regions. Second, the IRegionMemberLifetime will not ensure that the same instance of the view will be used for all the navigation requests in the region, meaning that there could be several instances of the view in the region at the same time. This is where IsNavigationTarget comes into play. By keeping the instance alive and returning true in the IsNavigationTarget method, Prism will navigate to that existing view instead of creating a new one. Also, you can check the parameters passed in the navigation request before returning true or false in the IsNavigationRequest, giving you the flexibility to decide if you want to reuse a view or create a new one.

I hope this helps to give a more in deep understanding of the navigation API used by Prism.

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Sep 3, 2013 at 10:59 PM
Hi Damian,

I'm implementing IRegionMemberLifeTime and INavigationAware in my view models in order to being able to reuse views if no refresh is required. So, KeepAlive property returns true and IsNavigationTarget checks the navigation parameters to decide if current instance is suitable to be reused. The point is that there are instances that become no longer userful but still remain as deactivated at region. I've tried to set KeepActive to false at IsNavigationTarget as soon as the instance becomes unusefull but with no success. I'd like to clean up old unuseful instances in order to avoid memory leaks

Thanks

Best Regards

Daní
Developer
Sep 4, 2013 at 6:28 PM
Hi Dani,

Based on my understanding, the RegionMemberLifetimeBehavior (which is in charge of removing the views with KeepAlive = false) only acts when the corresponding view is deactivated. Therefore, if you change the KeepAlive property of an already deactivated view, the behavior won't remove it even if the active view was changed. You can see this in the OnActiveViewsChanged method of the aforementioned behavior:
private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action != NotifyCollectionChangedAction.Remove) return;

    var inactiveViews = e.OldItems;
    foreach (var inactiveView in inactiveViews)
    {
        if (!ShouldKeepAlive(inactiveView))
        {
            this.Region.Remove(inactiveView);
        }
    }
}
A possible approach to discard the unused views could be to manually remove them from the region. For example, when the view no longer is needed, instead of setting the KeepAlive property to false it could publish an event using the EventAggregator. The parent view model would be subscribed to this event and remove the corresponding view.

Another approach could be to change the aforementioned method of the behavior so that it would check the KeepAlive property of all the views and not only of the deactivated one. You can replace the original behavior overriding the ConfigureDefaultRegionBehaviors method of you Bootstrapper.
You can find more information about this in the following section of the Prism documentation: Appendix E - Extending Prism - Region Behaviors

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini