Search code examples
wpfprism

Prism 6.3: Reuse View with different View Model's


I'm in a scenario to reuse a view with two completly independent view models.

For example you can think a generic list view to show apples somewhere and somewhere else to show cars. Doesn't really matter.

In Prism.Forms for Xamarin im able to glue a view with a viewModel like this.

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

I can't find an equivalent in Prism WPF, can someone help me out?


Solution

  • 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.