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
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));
}
}
}