PRISM WPF - Navigation creates new view every time

2019-02-07 05:23发布

问题:

I'm using PRISM 4 Navigation API with Unity in WPF. I have a tree-view that initiates a RequestNavigate passing in the selected tree node's ID (GUID).

_regionManager.RequestNavigate(RegionNames.DetailRegion,
    ViewNames.SiteView + "?ID=" + site.ID);

In my module, I have registered the view/view-model like so:

_container.RegisterType<SiteDetailsViewModel>();
_container.RegisterType<object, SiteDetailsView>(ViewNames.SiteView);

When I select different nodes from the tree view, the DetailsRegion displays the SiteDetailsView as expected, but when I like to navigate back to the same node, a new view/view-model is created.

I tried to break at IsNavigationTarget(NavigationContext navigationContext) but this method appears to never be called.

Where have i gone wrong? Thanks in advance.

回答1:

The problem was in such a place that I never expected... Debugging the Navigation API lead me to the RegionNavigationContentLoader

public object LoadContent(IRegion region, NavigationContext navigationContext)

When i stepped further down the code, I noticed a call to:

protected virtual IEnumerable<object> GetCandidatesFromRegion(
    IRegion region,
    string candidateNavigationContract)

I noticed that the naming here is key to matching the view to the view-model.

In my example, the name for each part was:

public class SiteDetailsViewModel { ... } // ViewModel

public class SiteDetailsView { ... } // View

ViewNames.SiteView = "SiteView" // ViewName constant

When I inadvertently made the following change:

ViewName.SiteView = "SiteDetailsView"

Everthing worked.

Conclusion

The name of the ViewModel must start with the same name you used to identify your view.

I tested this out by changing my view to:

public class MyView { ... }

and still using the same view name to register with the container and navigation:

_container.RegisterType<object, MyView>(ViewNames.SiteView);

...

_regionManager.RequestNavigate(RegionNames.DetailRegion,
    ViewNames.SiteView + "?ID=" + site.ID);

This seems to work also. So it seems the name of the View-Model is intrinsically linked to the view name used to navigate to that view.

NOTE

This is only when you're using IoC and Unity with the PRISM 4 Navigation API. This doesn't seem to happen when using MEF.

Further Investigation

I am also aware that some guides have told us to use the typeof(MyView).FullName when registering the view with the Container...

_container.RegisterType<object, MyView>(typeof(MyView).FullName);

I personally think this is a mistake. By using the view's full name, you are creating a depending between the view and any one who wishes to navigate to that view...

_regionManager.RequestNavigate(RegionNames.DetailRegion,
    typeof(MyView).FullName + "?ID=" + site.ID);


回答2:

The registration of the View and the ViewModel is the problem. To have only one view you have to use a different lifetime manager. Without specifying a lifetime manager the TransientLifetimeManager is used which always returns a new instance on resolve. To have only one single instance you have to use the ContainerControlledLifetimeManager or the HierarchicalLifetimeManager:

_container.RegisterType<SiteDetailsViewModel>(new ContainerControlledLifetimeManager());
_container.RegisterType<object, SiteDetailsView>(ViewNames.SiteView, new ContainerControlledLifetimeManager());