Mvvm Light & Wpf - Binding Multiple Instances of a Window to a Viewmodel

MVVM Light & WPF - Binding Multiple instances of a Window to a ViewModel

Ok I put together a demo that should make this hopefully easier for you Download Link

Functionality:

  • 3 Windows in Total (MainWindow, ModalWindow, NonModalWindow)
  • MainWindow has a TextBox you can type whatever you want into.
  • 2 buttons on the top will open the Modal / NonModal Window accordingly
  • Each window when opened will display the message that was in MainWindow's TextBox in a TextBlock inside them.
  • In each window you can tick a CheckBox to update the value in result's textblock in MainWindow (For the Modal Window this will kick in when modal window is closed. For NonModal changes can be seen asap)

That's it for functionality,

Concepts:

  • Registering Multiple VM's with the SimpleIoC and using GetInstance(...) to request them out.
  • Messenger class usage with a custom message type OpenWindowMessage
  • Opening Modal / Non Modal Windows from a parent VM staying true to the MVVM principles
  • Passing data between windows(just shown in NonModal)

Important Note:
- The method used in this example to set the non DP DialogResult from the modal window is not MVVM friendly cos it uses code-behind to set the DialogResult property on a Window.Closing event which should be avoided(If needing to be "testable"). My preferred approach is a bit long and is very well documented HERE(Mixture of question and answer). Hence why I ignored it for the sake of this sample.

How to have Multiple unique instances of ViewModel using MVVM Light?

You are not obliged to use ONLY the static view models in the view model locator. That approach only makes sense if your views are sharing the same view model instance. For your scenario, you would simply new up an instance of your view model and assign it to the DataContext property of each window you create.

public void ShowChildWindow(Window parent)
{
var window = new WindowView();
window.DataContext = new ViewModel();
window.Show();
}

Blendability for ViewModel instances in MVVM Light

Converting my comment to an answer

AFAIK you can get a new VM instance by passing a unique key while resolving the VM from ServiceLocator. You're not tied into having to use the VMLocator for just singleton's that way.

You can get an example of this procedure from Here. In MainWindow.xaml.cs when a new non-modal Window is requested, each instance of the view is coupled with a new instance of the corresponding VM, which can be found in the code-behind.

how could I bind a view-model instance to the view (that's not in the locator)

^^ Not really sure if this is what you're after, however with MVVM Light(for desgin time VM), you can just set DataContext of the View in it's constructor after you check if in design mode

something like:

using GalaSoft.MvvmLight;
...

public MainWindow() {
InitializeComponent();
if (ViewModelBase.IsInDesignModeStatic)
DataContext = new MainViewModel();
}

MVVM-Light Messenger With Multiple instances of the Same ViewModel

New Answer

As pointed out by the OP (Daryl), my original answer (see below) was not quite right, so I'm providing a new answer in case someone with the same problem comes across this later:

It makes sense that if you have two instances of something that are registering for the same message type with the same token, both instances will receive the message. The solution is to provide a token that is unique to each View-ViewModel pair.

Instead of just using a plain enum value as your token, you can place your enum value in a class, like this:

public class UniqueToken
{
public MessengerToken Token { get; private set; }

public UniqueToken(MessengerToken token)
{
Token = token;
}
}

Then in your ViewModel, add a new property to store one of these unique tokens:

// add a property to your ViewModel
public UniqueToken OpenWindowToken { get; private set; }

// place this in the constructor of your ViewModel
OpenWindowToken = new UniqueToken(MessengerToken.OpenWindow);

// in the appropriate method, send the message
Messenger.Send(message, OpenWindowToken);

Finally, in your View, you can now grab the unique token and use it to register for the OpenWindow message:

var viewModel = (MyViewModel)DataContext;
var token = viewModel.OpenWindowToken;
Messenger.Register<TMessage>(this, token, message => OpenWindow(message));

It is necessary for both the ViewModel and View to use a single instance of UniqueToken, because the messenger will only send a message if the receiver token and sender token are the exact same object, not just instances with the same property values.


Original Answer (not quite correct)

I think there may be a typo in your question: You say that to open a new window, you send a message from the ViewModel to the View, but then later you say both ViewModels are receiving the message. Did you mean both Views are receiving the message?

In any case, it makes sense that if you have two instances of something that are registering for the same message type with the same token, both instances will receive the message.

To solve this, you will first need each instance of your ViewModel to have a unique ID. This could accomplished with a Guid. Something like:

// add a property to your ViewModel
public Guid Id { get; private set; }

// place this in the constructor of your ViewModel
Id = Guid.NewGuid();

Then you would need your token to be an object that has two properties: one for the guid and one for the enum value:

public class UniqueToken
{
public Guid Id { get; private set; }
public MessengerToken Token { get; private set; }

public UniqueToken(Guid id, MessengerToken token)
{
Id = id;
Token = token;
}
}

Then when you register in your View (or is it your ViewModel?), you need to grab the Guid from the ViewModel. This could work like this:

var viewModel = (MyViewModel)DataContext;
var id = viewModel.Id;
var token = new UniqueToken(id, MessengerToken.OpenWindow);
Messenger.Register<TMessage>(this, token, message => OpenWindow(message));

Finally, in your ViewModel, you need to do something like this:

var token = new UniqueToken(Id, MessengerToken.OpenWindow);
Messenger.Send(message, token);

Edit

After typing all that out, it occurred to me that you don't really need an Id property on the ViewModel. You could just use the ViewModel itself as the unique identifier. So, for UniqueToken, you could just replace public Guid Id with public MyViewModel ViewModel, and it should still work.

WPF MVVM - How can I pass two variables from the ViewModel associated with the first window to the second ViewModel associated with the second window?

try this:

App.xaml

<Application.Resources>
<vm:MainViewModel x:Key="YourViewModel" />
</Application.Resources>

MainWindow.xaml

<Window x:Class="ABC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ABC"
DataContext="{StaticResource YourViewModel}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding SomeVar}"/>
</Grid>
</Window>

SecondWindow.xaml

<Window x:Class="ABC.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ABC"
DataContext="{StaticResource YourViewModel}"
mc:Ignorable="d"
Title="SecondWindow" Height="450" Width="800">
<Grid>
<TextBox Text="{Binding SomeVar}"/>
</Grid>
</Window>


Related Topics



Leave a reply



Submit