7
votes

I am new to both WPF and MVVM and a came across an issue when attempting to set the DataContext to the same instance of my ViewModel in two separate views.

This was because:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>

would create a new instance of the view model for each view.

To get around this I decided to create a class that stored static instances of each ViewModel I used. Then in the cs file of each view I would then set the DataContext to the appropriate ViewModel from this static class.

This works but doesn't seem the best idea for larger programs where the multiple instances of the ViewModel may be needed simultaneously.

What are better approaches to this problem - Are there sound ways to have multiple Views using the same instance of a ViewModel?

Or is this approach bad practice - Should I be designing a program with one View for every ViewModel?

5
I don't have any answer for you. I have never encountered a situation such as you describe where multiple views would have assigned one view model. Could you tell what you're trying to do? I am just wondering.Ladislav Ondris
Caliburn.Micro offers multiple views for one view-model via "cal:View.Context" property. Maybe you can have a look in their source code, how this is technically achieved.Sven Bardos
@LadislavOndris So I have created a simple tic tac toe game, the ViewModel contains an instance of the Game class - containing all the information needed for a game, and I have two separate views one for the game board (3x3 grid of buttons) and one for information of the game (whos turn it is, number of wins etc)Alfie

5 Answers

10
votes

You can instantiate that view model in App.xaml so that it is accessible to the whole application.

<Application.Resources>
    <local:ViewModel x:Key="sharedViewModel" />
</Application.Resources>

Then in your views when you want to use that datacontext, you do the following...

DataContext="{StaticResource sharedViewModel}"
2
votes

Simple and easy as well as one of the recommended approach is implementing ViewModelLocator.

Idea is having defined all the ViewModels in ViewModelLocator class and access the ViewModel wherever needed. Using Same ViewModel in different View will not be a problem here.

    public class ViewModelLocator
{
         private MainWindowViewModel mainWindowViewModel;
  public MainWindowViewModel MainWindowViewModel
    {
        get
        {
            if (mainWindowViewModel == null)
                mainWindowViewModel = new MainWindowViewModel();

            return mainWindowViewModel;
        }
    }
    private DataFactoryViewModel dataFactoryViewModel;
 public DataFactoryViewModel DataFactoryViewModel
    {
        get
        {
            if (dataFactoryViewModel == null)
                dataFactoryViewModel = new DataFactoryViewModel();

            return dataFactoryViewModel;
        }
    }
}

App.xaml

    xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

<Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>

Usage

<Window ...
  DataContext="{Binding Path=MainWindowViewModel, Source={StaticResource ViewModelLocator}}">

refer : So Question codes copied from there.. as i cannot rip the codes from my project..

1
votes

I had this same question and I couldn't find a good answer. After thinking about it for a while I came to the conclusion that in most cases it's best to create a one to one mapping between view model and view. So in this situation I would create two separate view models that inherit from a base view model. That way you can put whatever is common in the base view model and add any fields or methods that might be different to the more specific view model. If the view models truly are equivalent then you might want to ask yourself why you have two separate views in the first place. You may consider merging them into one view. It's possible that having two separate views is what you want, but it's just something to consider.

0
votes

I am using the prism framework and was also looking for a solution of using one viewmodel for many (child) views. In prism there are two possible solutions:

  1. Define the viewmodel as singleton (code smell) or
  2. Using the RegionContext (for full description see the prism doc)

According to the prism doc my solution looks like this.

In the prism bootstrapper:

Container.RegisterTypeForNavigation<MyView>("MyView");

In the viewmodel:

private void DisplayView(string viewName)
{
    RegionManager.Regions["ContentRegion"].Context = this;  // set viewmodel
    RegionManager.RequestNavigate("ContentRegion", viewName);
}

In the code behind of each view:

public MyView()
{
    InitializeComponent();
    ObservableObject<object> viewRegionContext = RegionContext.GetObservableContext(this);
    viewRegionContext.PropertyChanged += this.ViewRegionContext_OnPropertyChangedEvent;
}

private void ViewRegionContext_OnPropertyChangedEvent(object sender, PropertyChangedEventArgs args)
{
    if (args.PropertyName == "Value")
    {
        var context = (ObservableObject<object>)sender;
        DataContext = context.Value;  // get viewmodel as DataContext
    }
}
0
votes

The staticresource/singleton approach (is this what it's called? I'm bad at programming terminology) by @Fabulous is great because it's simple code that's easy to implement, but it also means that your code can execute even when you didn't explicitly want it to and that can cause some headaches.

In my case, it would execute e.g. when rebuilding (not too bad, but not great), when re-opening the solution (ugh), and when editing parts of app.xaml (which would for some reason softlock VS2019 i.e. very bad). As I understand it, people who Know What They're Doing probably already knew this would happen, but it was a pain in the ass as a novice programmer to figure out what was going on haha.

One way to keep the simplicity benefits without ruining your day with phantom-running code is to implement a simple check on whatever your main code is to see if you're running in designtime or not (basically a simplified version of @WPFUser's link).

Example:

using System.ComponentModel;
using System.Windows;

public MainCode()
{
    if (IsInDesignMode() == false)
    {
        //execute code normally
    }
    else
    {
        //do nothing (or maybe display an info message or something)
    }
}

private DependencyObject dummy = new DependencyObject();

private bool IsInDesignMode()
{
    return DesignerProperties.GetIsInDesignMode(dummy);
}

(plus still use all the stuff noted in Fabulous' answer)

I haven't tested this extensively yet so be aware that it may not be 100% foolproof, but so far for me it's worked great in the ways I've tested it.