Lets define :
- a viewModel : the
TabViewModel
class - a view : the
TabView
class
I have n instances of the TabView
class, and so n instances of TabViewModel
.
When one instance of the TabView
class send a message, I want it to be received by its own viewmodel, and only this one.
As I understand the Messenger of the mvvm light toolkit, I whould use something like :
// in the view
Messenger.Default.Send(new RefreshMessage(/*...*/), oneToken);
and
// in the viewmodel
Messenger.Default.Register<RefreshMessage>(this, oneToken, MyViewModelMethod);
What should I use for oneToken
?
My first thought was to use the ViewModel instance as token :
// in the view
Messenger.Default.Send(new RefreshMessage(/*...*/), this.DataContext);
and
// in the viewmodel
Messenger.Default.Register<RefreshMessage>(this, **this**, MyViewModelMethod);
This seems "mvvm-friendly" to me, because the view doesn't know what is the DataContext. But with this solution, I fear a memory leak : in mvvm light, the recipients are weak-referenced, but the token is not (as you will see in the WeakActionAndToken struct of the Messenger class.
What can I use as token ? Is the viewmodel instance a good choice, and how can I prevent memory leak if I use it ?
EDIT : Possible SOLUTIONS
Option 1 (based on ethicallogics answer):
- Define a Token property (of type string or GUID for example) on both the view and the viewmodel
- Define the value of one of them (a unique value, set it for example in the constructor of the viewmodel)
- Bind them together in XAML
- Use them in the Messenger call
Option 2 (the one I've taken) :
Use the viewmodel instance as Token.
To prevent a memory leak, we must encapsulate it in a weakReference. In order to work with the Messenger, which compares 2 tokens, the weakReference shoud have the Equals
method implemented (which is not the case of the default .Net implementation of the WeakReference
class).
So we have :
// in the view
Messenger.Default.Send(new RefreshMessage(), new EquatableWeakReference(this.DataContext));
and
// in the viewmodel
Messenger.Default.Register<RefreshMessage>(this, new EquatableWeakReference(this), ApplyRefreshMessage);
I implemented the EquatableWeakReference
class as follow :
/// <summary>
/// A weak reference which can be compared with another one, based on the target comparison.
/// </summary>
public class EquatableWeakReference : IEquatable<EquatableWeakReference>
{
private WeakReference reference;
private int targetHashcode;
public EquatableWeakReference(object target)
{
if (target == null)
throw new ArgumentNullException("target");
reference = new WeakReference(target);
targetHashcode = target.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as EquatableWeakReference);
}
/// <summary>
/// As Equals is overriden, we must provide an override for GetHashCode.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return targetHashcode;
}
public bool Equals(EquatableWeakReference other)
{
if (other == null
|| !reference.IsAlive
|| !other.reference.IsAlive)
return false; // we assume that if both references are not alive, the result is inconclusive : let's say false.
return this.reference.Target.Equals(other.reference.Target);
}
}
Advantage is a lightweight code on both the view and the viewmodel, with no memory leak. Tested successfully. Feel free to comment if you have a better solution.