Prism 6.3: Reuse View with different View Model's

Solution 1:

The link that @AdamVincent posted and the "missing" methods are very useful for normal view/viewmodel navigation using the ViewModelLocationProvider. However when trying to use two view models for the same view they don't work. This is because inside the extension method there is a call that registers the viewmodel to the view for use by the ViewModelLocationProvider.

private static IUnityContainer RegisterTypeForNavigationWithViewModel<TViewModel>(this IUnityContainer container, Type viewType, string name)
{
    if (string.IsNullOrWhiteSpace(name))
        name = viewType.Name;

    ViewModelLocationProvider.Register(viewType.ToString(), typeof(TViewModel));

    return container.RegisterTypeForNavigation(viewType, name);
}

Internally, ViewModelLocationProvider.Register uses a dictionary to store the association between view models and views. This means, whe you register two view models to the same view, the second will overwrite the first.

Container.RegisterTypeForNavigation<PageA, ViewModelA>("PageA1");
Container.RegisterTypeForNavigation<PageA, ViewModelB>("PageA2");

So with the above methods, when using the ViewModelLocationProvider, it will always create an instance of ViewModelB because it was the last one to be registered.

Additionally, the next line calls RegisterTypeForNavigation which itself ultimately calls Container.RegisterType, is only passing the viewType.

To resolve this, I tackled it a different way using an Injection property. I have the following method to bind my viewmodel to my view

private void BindViewModelToView<TView,TViewModel>(string name)
{
    if (!Container.IsRegistered<TViewModel>())
    {
        Container.RegisterType<TViewModel>();
    }

    Container.RegisterType<TView, TViewModel>(name,new InjectionProperty("DataContext", new ResolvedParameter<TViewModel>()));
}

We know each view will have a DataContext property, so the Injection property will inject the viewmodel directly into the DataContect for the view.

When registering the viewmodels, instead of using RegisterTypeForNavigation, you would use the following calls:

BindViewModelToView<PageA,ViewModelA>("ViewModelA");
BindViewModelToView<PageA,ViewModelB>("ViewModelB");

To create the view, I already have a method that I use to inject the appropriate view into my region, and it works using the viewname as the key to obtain the correct viewmodel instance.

private object LoadViewIntoRegion<TViewType>(IRegion region, string name)
{
    object view = region.GetView(name);
    if (view == null)
    {
        view = _container.Resolve<TViewType>(name);     
        if (view is null)
        {
            view = _container.Resolve<TViewType>();
        }
        region.Add(view, name);
    }
    return view;
}

Which I simply call with

var view = LoadViewintoRegion<PageA>(region,"ViewModelA");

and

var view = LoadViewintoRegion<PageA>(region,"ViewModelB");

So for normal single View/Viewmodels, I use the ViewModelLocationProvider.AutoWireViewModel property and where I have multiple viewmodels, I use this alternative approach.

Solution 2:

2022/01/15 Update

First of all thanks a lot for Jason's answer, his answer is great, and I implemented it perfectly with reference to his design, but because of Prism version update, I made some changes

I have an single view with multiple viewmodel

Step 1

register your view region

<ContentControl prism:RegionManager.RegionName="{x:Static hard:RegionNames.PanelPosCameraRegion}"></ContentControl>

Step 2

coding your viewmodel

private IRegionManager _RegionManager;
private IUnityContainer _UnityContainer;

public ICommand LoadedCommand { get; set; }

public RoboticPageVM(IRegionManager regionManager, IUnityContainer unityContainer)
{
    _RegionManager = regionManager;
    _UnityContainer = unityContainer;
    LoadedCommand = new DelegateCommand(LoadedCommandHandle);
}

private void LoadedCommandHandle()
{
    BindViewModelToView<PanelPosMultiplexView, PanelPosCameraVM>("Camera");
    BindViewModelToView<PanelPosMultiplexView, PanelPosAxisVM>("Axis");
    
    LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosCameraRegion, "Camera");
    LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosAxisRegion, "Axis");
}

private void BindViewModelToView<TView, TViewModel>(string registerName)
{
    if (!_UnityContainer.IsRegistered<TViewModel>())
    {
        _UnityContainer.RegisterType<TViewModel>();
    }
    _UnityContainer.RegisterType<TView>(registerName, new InjectionProperty(nameof(UserControl.DataContext), new ResolvedParameter<TViewModel>()));
}

private void LoadViewIntoRegion<TView>(string regionName, string registerName)
{
    IRegion region = _RegionManager.Regions[regionName];
    object? view = region.GetView(registerName);
    if (view == null)
    {
        view = _UnityContainer.Resolve<TView>(registerName);
    }
    if (!region.Views.Any(v => v.GetType() == typeof(TView)))
    {
        region.Add(view, registerName);
    }
}