Connect properties between ViewModels

Datetime:2016-08-23 01:26:01          Topic: MVVM Model           Share

Introduction

I recently started a rather large project and found myself inclined to try and leverage the separation of concerns that the MVVM pattern tries to offer. However before you can start programming using the MVVM pattern in WPF, you realize that the standard .NET library is not enough, so you are going to need a lot of tools in order to be able to separate the code as desired. I Immediately went for PRISM as my standard toolset and that helped me a lot, especially with the connection between the View and the ViewModels.

But when I came to the design of the ViewModels, I got to the point where I had several different ViewModels that needed to talk to each other. After a bit of searching and not surprisingly,Sacha Barbers Cinch came up, with something called a Mediator.

His implementation of the Mediator is basically a class that has a shared (static) instance and a shared (static) Dictionary of all the items that should be connected. In Cinch you can raise an "event" in a property, that will send a delegate to a void in another ViewModel  (or you can do it in the same also, but then there is little point in using the Mediator). The schematics below show his implementation, and taken from his article about Cinch and the Mediator (CinchV2 :Version 2 of my Cinch MVVM framework: Part 4 of n ):

The image shows a one-way communication between a property in ViewModelY and a subroutine (void) in ViewModelX, and the connection is configured and maintained inside the static Mediator. My problem with this design was that it was just that, one-way. I had some instances where I really needed a two-way communication between two or more properties residing in different ViewModels, so I dived into the Mediator class to see how I could change it to fit my needs. I called my new shared class a PropertyMediator to describe that it would work on properties alone.

A weak problem

The connection between properties in WPF can easily be resolved using the event that is tailored made in the PropertyDescriptor . The problem in using that inside the mediator is that each time the event is hooked, it will create a strong reference, similar to the binding between the ViewModel and the View. This will, in turn, make the garbage collector to believe that the class is still in use, so it can't be collected to free up the unused memory.  

The problem with a ViewModel in MVVM is that it can be disconnected from the View without the information beeing propagated to the Mediator. This will in turn then keep the unused ViewModel via either a handle or a strong referance, and if you do this multiple times with the same ViewModel, you will have a ton of unused classes holding on to your computer memory for dear life.

The solution to this problem is to implement WeakReferance and WeakEvents inside the Mediator. The weak pattern basically tells the garbage collector that if there are no one other than the weak event listeners or weak references connected to the class, you can remove the class from memory. However, using WeakEvents does not mean that you can subscribe to events and forget about them, you have to unsubscribe to them as they were a normal event.

A WeakEvent is quite straight forward to implement, and will function as a normal event:

public class StudentViewModel:NotifierBase
   {
       public StudentViewModel()
       {
           StudentViewModel student = new StudentViewModel();
           WeakEventManager<StudentViewModel, PropertyChangedEventArgs>.AddHandler(student, "PropertyChanged", newStudent_PropertyChanged);
           WeakEventManager<StudentViewModel, PropertyChangedEventArgs>.RemoveHandler(student, "PropertyChanged", newStudent_PropertyChanged);
       }

       void newStudent_PropertyChanged(object sender, PropertyChangedEventArgs e)
       {
           if (e.PropertyName.Equals("Name"))
           {

           }
       }

       private string m_Name = "Name";
       public string Name
       {
           get { return m_Name; }
           set
           {
               SetProperty(ref m_Name, value);
           }
       }

   }

But with reflection it is not so straigt forward. First off a big thanks toSascha Lefèvrewho showed me how to implement it. First off you need to create a type mold that you want to specify the number of inputs that the WeakEventManager will take, and then what types of arguments you want it to take. 

Type unboundWEMType = typeof(WeakEventManager<,>);
Type[] typeArgs = { _sender.Target.GetType(), typeof(EventArgs) };
Type constructedWEMType = unboundWEMType.MakeGenericType(typeArgs);

You then make the generic type, and you will have the same as was done in the StudenViewModel:

WeakEventManager<SenderType, EventArgs>

I had some problems here, as it seemed that the reflected type insisted in using the generic arguments of EventArgs. If I tried to use PropertyChangedEventArgs it would just throw an error saying that it was unable to cast it. That meant that I had to cast the EventArgs into PropertyChangedEventsArgs afterwords:

EventHandler<EventArgs> handler = new EventHandler<EventArgs>(WeakPropertyDescriptor_PropertyChanged);

private void WeakPropertyDescriptor_PropertyChanged(object sender, EventArgs e)
           {
               PropertyChangedEventArgs arg = (PropertyChangedEventArgs)e;
               if (arg.PropertyName == _PropertyName)
               {
                        ...
               }

           }

All that was left was to use the MethodInfo to create the AddHandler and RemovHandler methods:

MethodInfo addHandlerMethod = constructedWEMType.GetMethod("AddHandler");
addHandlerMethod.Invoke(null, new object[] { _sender.Target, "PropertyChanged", handler });

The WeakReferance is much easier to implement:

WeakReference WeakSender = WeakReferance(Sender);

it also holds the object that Sender was in WeakSender.Target and a property WeakSender.IsAlive that indicateds if the original object is bound to any other object.

How to use it in a ViewModel

So, you have registered the Mediator in the class you want to use it in, as shown below:

public class A : NotifierBase
   {
       public A()
       {
           PropertyMediator.Instance.Register(this);
       }

       ...

   }

This will enable the PropertyMediator to loop trough all the properties in the class and look for a decorated properties with my special PropertyMediatorAttribute:

[AttributeUsage(AttributeTargets.Property)]
public sealed class PropertyMediatorAttribute : Attribute
{
    /// <summary>
    /// Message key
    /// </summary>
    public string MessageKey { get; private set; }
    public bool SendAsync { get; private set; }

    /// <summary>
    /// Default constructor
    /// </summary>
    public PropertyMediatorAttribute()
    {
        MessageKey = null;
        SendAsync = false;
    }

    /// <summary>
    /// Constructor that takes the massage key and an optional boolean to update it async.
    /// </summary>
    /// <param name="messageKey">Message key, a unique binder name</param>
    /// <param name="sendAsync">Optional boolean send async, is false by default</param>
    public PropertyMediatorAttribute(string messageKey, bool sendAsync = false)
    {
        MessageKey = messageKey;
        SendAsync = sendAsync;
    }
}

As you can see it will decorate the property with a unique identifier called MessageKey, and it will use it to connect different properties with inside the Mediator. It also has an optional argument to allow properties to be send async, it is set to false if not specified.

The property in the Class A is thus implemented as follows (with async updating of other properties enabled):

private string m_Name = "Class A";

[PropertyMediatorAttribute("MyProp", true)]
public string Name
{
    get { return m_Name; }
    set
    {
        SetProperty(ref m_Name, value);
    }
}

Please note that for the property to be updated it needs to implement INotifyPropertyChanged for the Mediator to react to the changes, SetProperty is just a helper subroutine that implements it. You should also make sure that the INotifyPropertyChanged event isn't fired if the value is equal to the current value.

The last thing that you could do, is to unsubscribe to the Mediator on Dispose:

~A()
{
    PropertyMediator.Instance.UnRegister(this);
}

Strictly speaking, this isn't necessary, because the property would be removed as soon as someone tries to update it via the Mediator. This will just force the PropertyMediator to remove it from its watch list immediately.

Inside the PropertyMediator

The PropertyMediator relies heavily on the use of reflection to initialize the weak listen handlers. First off we loop trough all the properties in the class the is registered, and if the property is decoreted with a PropertyMediatorAttribute it shoud store all necessary objects of this property instance in a Dictionary with the key that is equal to the attributes MessageKey.

public void Register(object view)
    {
        // Look at all instance/static properties on this object type.
        foreach (var mi in view.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
        {
            //  var test = mi.GetCustomAttributes(typeof(PropertyMediatorAttribute));
            // See if we have a target attribute - if so, register the method as a handler.
            foreach (var att in mi.GetCustomAttributes(typeof(PropertyMediatorAttribute)))
            {
                var mha = (PropertyMediatorAttribute)att;
                RegisterProperty(mha.MessageKey, view, mi.Name, mha.SendAsync);
            }
        }
    }

The Dictionary were the varibles are stored is decleard static, and the values are a list of an internal class called WeakPropertyDescriptor. All items with the same MessageKey will be added in the list of WeakPropertyDescriptor:

private readonly Dictionary<string, List<WeakPropertyDescriptor>> _registeredListners =
    new Dictionary<string, List<WeakPropertyDescriptor>>();

since this Dictionary is shared between all PropertyMediator subscribers, it is important to lock the dictionary when you are doing something to it:

lock (_registeredListners)

This will prevent any other instances to make any changes until you relese the lock.

All the insteresting bits now happens in the internal class:

internal class WeakPropertyDescriptor
       {
           private PropertyMediator _MethodOwner;
           public WeakReference _sender;
           public string _PropertyName;
           private string _MessageKey;
           private EventHandler<EventArgs> handler;
           private Type constructedWEMType;
           private bool _SendAsync;

           public WeakPropertyDescriptor(PropertyMediator MethodOwner, Object sender, string PropertyName, string MessageKey, bool SendAsync)
           {
               _MethodOwner = MethodOwner;
               _sender = new WeakReference(sender);
               _PropertyName = PropertyName;
               _MessageKey = MessageKey;
               _SendAsync = SendAsync;

               handler = new EventHandler<EventArgs>(WeakPropertyDescriptor_PropertyChanged);

               Type unboundWEMType = typeof(WeakEventManager<,>);
               Type[] typeArgs = { _sender.Target.GetType(), typeof(EventArgs) };
               constructedWEMType = unboundWEMType.MakeGenericType(typeArgs);

               this.AddHandler();
           }

           public bool HasBeenCollected
           {
               get
               {
                   return (_sender == null || !_sender.IsAlive);
               }
           }

           private void WeakPropertyDescriptor_PropertyChanged(object sender, EventArgs e)
           {
               PropertyChangedEventArgs arg = (PropertyChangedEventArgs)e;
               if (arg.PropertyName == _PropertyName)
               {
                   if (_SendAsync)
                       _MethodOwner.NotifyColleaguesOfVauleChangedAsync(_sender.Target, e, _MessageKey, _PropertyName);
                   else
                       _MethodOwner.NotifyColleaguesOfValueChanged(_sender.Target, e, _MessageKey, _PropertyName);
               }

           }

           public void AddHandler()
           {
               MethodInfo addHandlerMethod = constructedWEMType.GetMethod("AddHandler");
               addHandlerMethod.Invoke(null, new object[] { _sender.Target, "PropertyChanged", handler });
           }

           public void RemoveHandler()
           {
               MethodInfo removeHandlerMethod = constructedWEMType.GetMethod("RemoveHandler");
               removeHandlerMethod.Invoke(null, new object[] { _sender.Target, "PropertyChanged", handler });
           }
       }

And that is really all that is too it. The other code in the PropertyMediator is just there to update the Dictionary by adding and removing elements or keys, as well as updating the connected properties.

When to use the PropertyMediator

I intended this to be used for user inputs (from the same user) that could happen in different ViewModels, and hence it made sence to update or connect two properties together. You should be a bit careful when using this in an application, as you could easily set up two competing properties in code that are linked together, causing an everlasting tautology to happen.

If you need checks or any other things with this you migth want to implement this, as suggested by Pete O'Hanlon with the use of the Reactive Extensions . I've never used it before but this video on Channel 9 was really impressive, and the examples are really easy to use. However, in order to make it work for WeakEvents and WeakReferances you need to do a bit of tweeking for them to be implemented correctly, along the lines of this . I will hopefully make some changes that will implement the PropertyMediator using Rx in the future.





About List