Question

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):

  1. Define a Token property (of type string or GUID for example) on both the view and the viewmodel
  2. Define the value of one of them (a unique value, set it for example in the constructor of the viewmodel)
  3. Bind them together in XAML
  4. 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.

Was it helpful?

Solution

Token is a object unique value that View passes to the ViewModel and they both uses the same Token. like

View

public partial class MainWindow : Window
{
    readonly string Token;
    public MainWindow()
    {
        Token = Guid.NewGuid().ToString();
        InitializeComponent();
        DataContext = new MainViewModel(Token);
    }
}

ViewModel

public class MainViewModel 
{
    readonly string Token;

    public MainViewModel(string token)
    {
        Token = token;
    }
}

Actually the logic behind the Token is that when we Register a delegate to Messenger .It does have internally dictionary and this Token act as the key in that dictionary. View and its ViewModel must have same Token So that exact delegate corresponding to that key could be fired on Send method.

OTHER TIPS

If you're using MVVMLight, use a command. That is guaranteed to go to the right VM.

In the VM:

    this.DeletePreSaleCommand = new RelayCommand(() => this.DeletePreSale(), () => this.CanDeletePreSale());

This creates a RelayCommand which is a property on the VM; when the Command is called by the View it will call the method DeletePreSale() on the VM, but if the VM Method CanDeletePreSale() does not return true is will not allow the command to be invoked and will disable the widget the command is bound to.

In the View:

        <telerik:RadButton Grid.Row="3" Width="200" Command="{Binding DeletePreSaleCommand}"/>

Cheers -

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top