3

I am trying to have view models resolved using DI with Prism 6 and Unity in my WPF app and this works. However I don't know how to tell the framework which view should be merged with which view model.

If I use the convention, i.e. have ViewModels, and Views namespaces, and classes ViewA and ViewAViewModel everything works, however I would like to have more flexibility to name and organize my classes and this is why I want to somehow tell the framework explicitly which view goes with which view model. I tried many things, but nothing really works. Current "solution" makes app run but view model is not set.

Here is the code:

ViewA.xaml

<UserControl x:Class="WPFDITest.Views.ViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="{Binding ViewAMessage}"/>
        <TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</UserControl>

MainWindow.xaml

<UserControl x:Class="WPFDITest.Views.ViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="{Binding ViewAMessage}"/>
        <TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</UserControl>

ViewAVM.cs

public class ViewAVM : BindableBase
{
    private string viewAMessage;

    public ViewAVM(IModelA model)
    {
        viewAMessage = model.HelloMsgA();
    }

    public string ViewAMessage
    {
        get { return viewAMessage; }
        set { SetProperty(ref viewAMessage, value); }
    }
}

Model.cs

public interface IModelA
{
    string HelloMsgA();
}

public class ModelA : IModelA
{
    public string HelloMsgA()
    {
        return "Hello from A!";
    }
}

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var bootstraper = new Bootstrapper();
        bootstraper.Run();
    }
}

Bootstrapper.cs

public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow.Show();
    }

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
        Container.RegisterType<object, ViewAVM>("ViewA");
    }

    protected override void ConfigureViewModelLocator()
    {
        ViewModelLocationProvider.SetDefaultViewModelFactory(type => Container.Resolve(type));
    }
}
Łukasz
  • 8,555
  • 2
  • 28
  • 51

2 Answers2

4

After some digging through Prism sources I found out how to do what I want. I can register each view with ViewModelLocationProvider.Register passing in a factory method for the view model. I created method that does just that with convenient syntax and uses container to resolve view model for given type:

public void BindViewModelToView<TViewModel, TView>()
{
    ViewModelLocationProvider.Register(typeof(TView).ToString(), () => Container.Resolve<TViewModel>());
}

And there is how I use it to bind ViewAVM to ViewA and ViewB both using same singleton instance.

public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow.Show();
    }

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
        Container.RegisterType<ViewAVM>(new ContainerControlledLifetimeManager());
    }

    protected override void ConfigureViewModelLocator()
    {
        BindViewModelToView<ViewAVM, ViewA>();
        BindViewModelToView<ViewAVM, ViewB>();
    }
}

By the way, as far as I can tell by the sources, it is only possible to associate view to view model through with ViewModelLocator by registering factories or by using their or custom convention, don't look for some DI magic.

Łukasz
  • 8,555
  • 2
  • 28
  • 51
3

Here is a link to Brian's blog on the ViewModelLocator, and it includes a section (Change those Nasty Conventions) on how to override the conventions if you would like.

Getting Started with Prism’s new ViewModelLocator

Personally, I set my DataContext in the code behind of the UserControl, in the constructor, after the View gets registered with the container in the Module. Conventions be damned!! :)

public ProductView(ProductViewModel view_model)
{
    InitializeComponent();
    DataContext = view_model;
}
R. Richards
  • 24,603
  • 10
  • 64
  • 64
  • 1
    I've seen that post and a lot more things but still, I don't want conventions. I tried your approach as well, but the issue is whenever I make `ViewA` take `ViewAVM` as parameter and set its `DataContext` I get a `NullReferenceException` in `MainWindow` constructor in `InitializeComponent` method. – Łukasz May 04 '16 at 20:37
  • You can clone this repo https://github.com/lukaszwawrzyk/wpf-prism-di-test.git and see. – Łukasz May 04 '16 at 20:40
  • I don't have Git installed. If you want this to work the way I do it, then you need a Module, and the override for the CreateModuleCatalog in the bootstrapper. The CreateModuleCatalog is where you register Modules that implement the IModule interface. In the Initialize method of the Module, you register Views with the Container. The container sees that the View needs a ViewModel and provides one. This may be more than you want, or need. You could just go with DataContext = new ViewAVM(); Have you looked at the PrismLibrary examples on GitHub? – R. Richards May 04 '16 at 21:11
  • So you are saying that to have view models injected to views I need to go with modules. I don't want to create VM in code behind, I decided to go with DI as the project is going to grow and this also allows me to for example inject service for a popupwindowaction defined in xaml. I looked at the examples you gave but I didn't know I am looking for modules :p I will take a look at this module approach tomorrow. – Łukasz May 04 '16 at 21:21
  • Try switching the order in which you register your objects with the container. Add the MV first, then the view. It may be that simple. Perhaps the VM will resolve then. Wild guess!! – R. Richards May 04 '16 at 21:28
  • That didn't work. Thank you for all your help, I eventually found solution that satisfies me, I will post it in few minutes. – Łukasz May 05 '16 at 08:02