3

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.

JYL
  • 8,228
  • 5
  • 39
  • 63

2 Answers2

1

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.

yo chauhan
  • 12,079
  • 4
  • 39
  • 58
  • Thanks for your answer, which makes sense. However, in Mvvm Light Toolkit, the ViewModel is injected through a ViewModelLocator into the view, so I can't really do `DataContext = new MainViewModel(Token)`. – JYL Jan 05 '14 at 09:15
  • I mark your answer as accepted, since it can be a solution for those who don't use the viewmodelLocator of the toolkit. I added my own solution in the question. Thanks for your answer ! – JYL Jan 05 '14 at 10:45
0

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 -

simon at rcl
  • 7,326
  • 1
  • 17
  • 24
  • I use commands everywhere else, but here i want to react to the SizeChanged event. This event is managed in the view to simulate the "EndResize" event as described [here](http://stackoverflow.com/a/16423497/218873). So I can use neither a RelayCommand, nor an [EventToCommand](http://msdn.microsoft.com/en-us/magazine/dn237302.aspx). Thanks for reply anyway. – JYL Jan 04 '14 at 15:04
  • Makes sense. Yes, I'd use the VM/DataContext as the token. I followed your link re: the memory leak, but it didn't work. Can you check it? I use MVVMLight Messages a lot on my apps and haven't seen gthis behavior (but I'm not sending the DataContext as a parameter)... – simon at rcl Jan 04 '14 at 15:08
  • You could also use the GetHashCode() if the DataContext, which will equal this.GetHashCode() of the target VM instance. Sending an int should protect from the leaks. – simon at rcl Jan 04 '14 at 15:09
  • Link updated (was a space problem). Problem with HashCode is that there's a probability that an other viewModel has the same. – JYL Jan 04 '14 at 15:31
  • It's a very low one, and should only happen if you override the GetHashCode method, surely? – simon at rcl Jan 04 '14 at 15:34
  • Also, that link only mentions that NO memory leaks are caused. Do you have other evidence that leaks happen with the Messenger? I certainlt haven't seen any in Production. – simon at rcl Jan 04 '14 at 15:36
  • As I said, no leak for **recipients** because they're referenced with a `WeakReference`. It's not the case for **a token**. So if the token is a viewmodel, it will never be released (just tested right now). – JYL Jan 04 '14 at 15:57