mvvmdata-bindingviewmodellocator

MVVM View vs ViewModel binding issues


I have am having an issue when instantiating viewModels.

I am using a ViewModelLocator for the most part since I have to inject dependencies in most cases. However there are cases when I need to pass arguments to the ViewModel. From what I understand, I need to use the ViweModel-First approach for that. Which means I need to create a DataTemplate for ViewModel that is bound to the View at runtime. Making sure to include a constructor with the arguments I want to pass in.

The issue I am having is that when I create the ViewModel and pass in my arguments, the correct constructor is called. However because the ViewModel is bound to the view, the view calls the default parameterless constructor of the viewmodel.

Here is what the XAML looks like for the UserControl I am binding the ViewModel to:

<UserControl x:Class="MyView">
    <UserControl.DataContext>
      <viewModels:MyViewModel></viewModels:MyViewModel>
    </UserControl.DataContext>
</UserControl>

The data template looks like this:

<DataTemplate DataType="{x:Type viewModels:MyViewModel}">
   <views:MyView></views:MyView>
</DataTemplate>

Here is a sample ViewModel:

public class MyViewModel : ViewModelBase
{
  private MyModel _myModel;

  public MyViewModel()
  {
  }

  public MyViewModel(MyModel myModel)
  {
    _myModel = myModel;
  }
}

Once I create my viewModel through code using the correct constructor to pass arguments, the viewModel is created again by the view using the default parameterless constructor of the viewModel.

Can anyone explain why this is happening and shed some light on how to setup the viewmodel-first approach so that it works correctly? I am at a loss and I have been working on this all day.

Thanks, Tim


Solution

  • If you remove the following snippet from your UserControl and follow my remaining instructions, I believe you will have what you want:

    Remove this

    <UserControl.DataContext>
          <viewModels:MyViewModel></viewModels:MyViewModel>
    </UserControl.DataContext>
    

    Now, lets say that you have a UserControl or Window bound to a ViewModel that you want represented in your UserControl or Window. The way you would do this is to use a ContentControl inside your UserControl or Window in conjunction with a DataTemplate specified in a ResourceDictionary like so:

    .XAML:

    <Window x:Class="WPF_Sandbox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
        Title="MainWindow"
        x:Name="ThisControl">
        <Window.DataContext>
            <vm:MainWindowViewModel/>
        </Window.DataContext>
        <DockPanel LastChildFill="True">
            <ContentControl DockPanel.Dock="Left" Content="{Binding NavigationRegion}"/>
            <ContentControl DockPanel.Dock="Left" Content="{Binding ContentRegion}"/>
        </DockPanel>    
    </Window>
    

    The ContentControl will implicitly lookup the DataTemplate (in the hierarchy of ResourceDictionary objects) associated with the ViewModel that is bound to its Content property. So lets say that we set the ContentRegion property in the MainWindowViewModel to an instance of your MyViewModel like so:

    MainWindowViewModel.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace WpfApplication1.ViewModels
    {
        public class MainWindowViewModel : ViewModel
        {
            private object _navigationRegion;
            private object _contentRegion;
    
            public object NavigationRegion
            {
                get
                {
                    return _navigationRegion;
                }
                set
                {
                    _navigationRegion = value;
                    OnPropertyChanged(nameof(NavigationRegion));
                }
            }
    
            public object ContentRegion
            {
                get
                {
                    return _contentRegion;
                }
                set
                {
                    _contentRegion = value;
                    OnPropertyChanged(nameof(ContentRegion));
                }
            }
    
            public MainWindowViewModel()
            {
                ContentRegion = new MyViewModel(new MyModel());
            }
        }
    }
    

    And you have a ResourceDictionary specified like so:

    MyResourceDictionary.xaml

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="clr-namespace:WpfApplication1"
                        xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
                        xmlns:views="clr-namespace:WpfApplication1.Views">
        <DataTemplate DataType="{x:Type vm:MyViewModel}">
            <views:MyView/>
        </DataTemplate>
    
    </ResourceDictionary>
    

    And you have merged your ResourceDictionary with your Application.Resources in your App.xaml file like so:

    <Application x:Class="WpfApplication1.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="clr-namespace:WpfApplication1"
                 StartupUri="MainWindow.xaml">
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="MyResourceDictionary.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    </Application>
    

    The Framework is going to implicitly look for a DataTemplate for Data Type MyViewModel. The Framework will find the DataTemplate for MyViewModel in the ResourceDictionary we specified and see that it should be represented by the MyView user control. It's at this point that the Framework will render the MyView user control.

    And for posterity sake:

    ViewModel.cs

    using System.ComponentModel;
    
    namespace WpfApplication1.ViewModels
    {
        public abstract class ViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected string _title;
    
            protected string Title
            {
                get
                {
                    return _title;
                }
                set
                {
                    _title = value;
                    OnPropertyChanged(nameof(Title));
                }
            }
    
            protected void OnPropertyChanged(string propName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
            }
        }
    }