Search code examples
wpfmvvmviewviewmodeldatacontext

Binding View to DataContext from another ViewModel Class


I am using the MVVM pattern and came across this problem.

I would like to create a SpecificProductViewModel object by passing the product for which I want the specific view by parameter from my ProductListViewModel class (which contains all the products) with the following function:

private void OnProductNav(tblMATProduct product)
    {
        if (product != null)
        {
            CurrentProduct = product;
            SpecificProductVM = new SpecificProductViewModel(product);
            CurrentProductViewModel = SpecificProductVM;
            SpecificProductVM.Product = product;
        }
    }

I would then need to assign the DataContext of my SpecificProductView to my SpecificProductViewModel in the code-behind like this:

DataContext = new SpecificProductViewModel();

The problem however, is that a new object of SpecificProductViewModel gets created by doing so and the DataContext is therefore not using the data (tblMATProduct product) passed by parameter when the object was created from the ProductListViewModel class.

Would there be a way to assign the DataContext directly from the ProductListViewModel class or to be able to get the tblMATProduct product from the code-behind or in the xaml?

Thanks in advance!

Edit 1: I was thinking like Lennart by doing this:

DataContext = new ProductListViewModel(); 

The problem though is that the specificViewModel instance needs to be created right after the user clicks on a specific product.

Therefore, if we create a new instance of ProductListViewModel in the code-behind and assign it ProductListViewModel, the instance won't know on what the user clicked and won't call the function OnProductNav.

Edit 2 : Adding more precision.

class ProductListViewModel : ViewModelBase
{

  private SpecificProductViewModel SpecificProductVM;

  public tblMATProduct CurrentProduct { set; get; }

  public ViewModelBase CurrentProductViewModel
    {
        get { return _currentProductViewModel; }
        set { SetProperty(ref _currentProductViewModel, value); }
    }

  public ProductListViewModel() {
    ProductNavCommand = new MyICommand<tblMATProduct>(OnProductNav);
  }

  private void OnProductNav(tblMATProduct product)
    {
        if (product != null)
        {
            SpecificProductVM = new SpecificProductViewModel(product);
            CurrentProductViewModel = SpecificProductVM;
        }

    }
}

In my ProductListView, I have a DataGrid with all my products where the user can select a row (specific product) by clicking on it.

<Datagrid>
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="SelectionChanged">
          <i:InvokeCommandAction Command="{Binding ProductNavCommand}" 
    CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
          </i:EventTrigger>
   </i:Interaction.Triggers>
</DataGrid>

Here is my SpecificProductViewModel

class SpecificProductViewModel : ViewModelBase
{
    private tblMATProduct _product;


    public tblMATProduct Product
    {
        get { return _product; }
        set { SetProperty(ref _product, value); }
    }

    public SpecificProductViewModel()
    {

    }

    public SpecificProductViewModel(tblMATProduct product)
    {
        Product = product;
    }
}

And here is a few lines of code from SpecificProductView.xaml which will bind to SpecificProductViewModel.cs

<Grid Background="Red" Height="800" Width="1350" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBlock Text="{Binding Product.FormatID}" FontSize="32" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    <TextBlock Text="{Binding Product.SpecificProductVM, RelativeSource={RelativeSource  Mode=FindAncestor, AncestorType=UserControl}}" FontSize="32" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
    <TextBlock Text="{Binding Product.GradeID}" FontSize="32" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</Grid>

Edit 3: Adding another layer of precision

To answer mm8's question, as I have just started working with WPF and MVVM, I have always assumed that if the View's DataContext is binded to the corresponding ViewModel, the instance of the View will be created automatically when a ViewModel instance is created via the xaml Datatemplate in ProductListView.xaml:

<DataTemplate DataType = "{x:Type viewModels:SpecificProductViewModel}">
    <products:SpecificProductView/>
</DataTemplate>

And this is where I would usually create my DataContext.

SpecificProductView.xaml.cs

public partial class SpecificProductView : UserControl
{
    public SpecificProductView()
    {
        InitializeComponent();
        DataContext = new SpecificProductViewModel();
    }
}

Edit 4: Explaining how the Data Template is handled

Here is the ContentControl which launches the view of the SpecificProductView. It uses

    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row="0">
        <ContentControl Content="{Binding CurrentProductViewModel}"/>
    </Grid>

It binds to this which determines which product should be displayed in the SpecificProductView.

public ViewModelBase CurrentProductViewModel
{
    get { return _currentProductViewModel; }
    set { SetProperty(ref _currentProductViewModel, value); }
}

Solution

  • Since you are using a ContentControl that binds to the CurrentProductViewModel property that you set in your view model, you should simply remove the following part that sets the DataContext of the UserControl to an new instance of the view model from your XAML:

    <UserControl.DataContext>
        <viewModel:SpecificProductViewModel/>
    </UserControl.DataContext>
    

    The UserControl will then inherit the view model returned by the CurrentProductViewModel as its DataContext.

    It is generally speaking a bad idea to explicitly set the DataContext property of a UserControl, either in XAML as well as in the code-behind, as this breaks the inheritance of the DataContext from the parent element.