Search code examples
wpfxamlmvvmdependency-injectiondatatemplate

How to bind a DataTemplate datatype to a View which receives ViewModels a dependency injected by DI


I have a WPF application which implements navigation using MVVM, filling a different DataTemplate for each View within the same Window e.g. :

<Window.Resources>
    <DataTemplate DataType="{x:Type foo:FooViewModel}">
        <foo:FooView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type bar:BarViewModel}">
        <bar:BarView/>
    </DataTemplate>
<Window.Resources>

(Switching between Views/UserControls using MVVM is Rachel Lim's article which has inspired the aforementioned approach)

What happens now is that FooView gets FooViewModel automatically injected as a dependency by DI (which in my case is Microsoft.Extensions.DependencyInjection on .Net Core3 Preview) e.g. :

public partial class FooView : UserControl
{
    public FooView(FooViewModel fooViewModel)
    {  
        this.InitializeComponent();
        this.DataContext = fooViewModel;
    }
}

At this point obviously the DataTemplate complains because the FooView does not define a parameter-less ctor (as per reference Type '{0}' is not usable as an object element)

Is there any way to bypass this issue and let FooView to use FooViewModel as DataContext?


Solution

  • DataTemplate

    <DataTemplate DataType="{x:Type foo:FooViewModel}">
        <foo:FooView/>
    </DataTemplate>
    

    will assign an instance of FooViewModel to FooView.DataContext.

    this.DataContext = fooViewModel; line in FooView control is useless, because DataContext will be overwritten in such scenario. I would say, it is perfectly fine not to pass view model via contrustor. It can be accessed from DataContext:

    public partial class FooView : UserControl
    {
        public FooView()
        {  
            this.InitializeComponent();
        }
    
        private FooViewModel Vm { get { return this.DataContext as FooViewModel; } }
    }
    

    In the navigation pattern you are using, View is a receiver of ViewModel. Current ViewModel is set by AppViewModel:

    public class AppViewModel
    {
        // simplified properties
        public ViewModelBase CurrentViewModel {get; set;}
        public ICommand ViewFooCommand {get;}
        public ICommand ViewBarCommand {get;}
    } 
    

    You are trying to make View an originator/producer of ViewModel, which conflicts with pattern.