Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM-Light Messenger With Multiple instances of the Same ViewModel

I am relativly new at MVVM, and have run into a problem. We are writing a database application in WPF using the MVVM-Light framework. The specs of the program state we must be able to have multiple instances of the ClaimView open at once.

To open new windows we are sending a Message from the ViewModel that is caught in the View, and opens the new window. We are using Enumerated tokens to identify the correct recipient to get the request.

Now, if I have 2 instances of the ClaimView open at once, and I call the Messanger, it opens 2 of the same windows, because both Views are recieveing the message.

We have tried running each instance of the ViewModel on a seperate thread, and verified by outputing the ManagedThreadId, and the message is still being recieved by both instances.

We have unregistered the Registered Message also, so that is not the problem.

Any help would be appreciated.

like image 324
Daryl Behrens Avatar asked Aug 28 '13 00:08

Daryl Behrens


1 Answers

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.

like image 55
devuxer Avatar answered Sep 29 '22 11:09

devuxer