Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVVM Mediator Pattern

0.00/5 (No votes)
9 Apr 2009 1  
MVVM Mediator pattern

About a year ago, a good friend of mine, Marlon Grech wrote a lovely article on MVC + M. You can read all about Marlon's great article here.

Essentially, what Marlon did was create a message system to allow disparate MVC classes to communicate with each other. The original code by Marlon was spot on, but I just didn’t like the way he was using strings for his messages, but other than that, there is very little change from my code here to his code. So well done Marlon, you are ace.

Anyway, the idea behind Marlon's original post was using the MVC pattern along with an additional class called the Mediator that knew about messages and who to notify when something happened that warrants a message being sent out.

These days, most folk will use the MVVM pattern when developing WPF. I have a small demo app that does the following.

  1. It has 2 textboxes, one of which is a writable textbox, which is used to send messages via the Mediator
  2. The 2nd textbox is refreshed via the message that is sent via the Mediator when the 1st textbox changes

Let's have a look at some code:

First, a helper class (lifted from Marlon's blog):

  1:  using System;
  2:  using System.Collections.Generic;
  3:  using System.Linq;
  4:  using System.Text;
  5:
  6:  namespace MediatorDemo
  7:  {
  8:      /// <summary>
  9:      /// The multi dictionary is a dictionary that contains
 10:      /// more than one value per key
 11:      /// </summary>
 12:      /// <typeparam name="T">The type of the key</typeparam>
 13:      /// <typeparam name="K">The type of the list contents</typeparam>
 14:      public class MultiDictionary<T, K>
 15:          : Dictionary<T, List<K>>
 16:      {
 17:
 18:          #region Private Methods
 19:          //checks if the key is already present
 20:          private void EnsureKey(T key)
 21:          {
 22:              if (!ContainsKey(key))
 23:              {
 24:                  this[key] = new List<K>(1);
 25:              }
 26:              else
 27:              {
 28:                  if (this[key] == null)
 29:                      this[key] = new List<K>(1);
 30:              }
 31:          }
 32:          #endregion
 33:
 34:          #region Public Methods
 35:          /// <summary>
 36:          /// Adds a new value in the Values collection
 37:          /// </summary>
 38:          /// <param name="key">The key where to place the
 39:          /// item in the value list</param>
 40:          /// <param name="newItem">The new item to add</param>
 41:          public void AddValue(T key, K newItem)
 42:          {
 43:              EnsureKey(key);
 44:              this[key].Add(newItem);
 45:          }
 46:
 47:
 48:          /// <summary>
 49:          /// Adds a list of values to append to the value collection
 50:          /// </summary>
 51:          /// <param name="key">The key where to place the item in the value list</param>
 52:          /// <param name="newItems">The new items to add</param>
 53:          public void AddValues(T key, IEnumerable<K> newItems)
 54:          {
 55:              EnsureKey(key);
 56:              this[key].AddRange(newItems);
 57:          }
 58:
 59:          /// <summary>
 60:          /// Removes a specific element from the dict
 61:          /// If the value list is empty the key is removed from the dict
 62:          /// </summary>
 63:          /// <param name="key">The key from where to remove the value</param>
 64:          /// <param name="value">The value to remove</param>
 65:          /// <returns>Returns false if the key was not found</returns>
 66:          public bool RemoveValue(T key, K value)
 67:          {
 68:              if (!ContainsKey(key))
 69:                  return false;
 70:
 71:              this[key].Remove(value);
 72:
 73:              if (this[key].Count == 0)
 74:                  this.Remove(key);
 75:
 76:              return true;
 77:          }
 78:
 79:          /// <summary>
 80:          /// Removes all items that match the prediacte
 81:          /// If the value list is empty the key is removed from the dict
 82:          /// </summary>
 83:          /// <param name="key">The key from where to remove the value</param>
 84:          /// <param name="match">The predicate to match the items</param>
 85:          /// <returns>Returns false if the key was not found</returns>
 86:          public bool RemoveAllValue(T key, Predicate<K> match)
 87:          {
 88:              if (!ContainsKey(key))
 89:                  return false;
 90:
 91:              this[key].RemoveAll(match);
 92:
 93:              if (this[key].Count == 0)
 94:                  this.Remove(key);
 95:
 96:              return true;
 97:          }
 98:          #endregion
 99:      }
100:  }

This simply allows more than 1 object to be registered for a particular message.

Next, comes the Mediator which is a singleton, and know how to send and register messages against callback, which is what I have changed. In Marlon's original code, he used Strings, whereas I am now using Action<Object> delegates as callbacks. A minor improvement I feel.

 1:  using System;
 2:  using System.Collections.Generic;
 3:  using System.Linq;
 4:  using System.Text;
 5:
 6:  namespace MediatorDemo
 7:  {
 8:      /// <summary>
 9:      /// Available cross ViewModel messages
10:      /// </summary>
11:      public enum ViewModelMessages { UserWroteSomething = 1 };
12:
13:
14:      public sealed class Mediator
15:      {
16:          #region Data
17:          static readonly Mediator instance = new Mediator();
18:          private volatile object locker = new object();
19:
20:          MultiDictionary<ViewModelMessages, Action<Object>> internalList
21:              = new MultiDictionary<ViewModelMessages, Action<Object>>();
22:          #endregion
23:
24:          #region Ctor
25:          //CTORs
26:          static Mediator()
27:          {
28:
29:
30:          }
31:
32:          private Mediator()
33:          {
34:
35:          }
36:          #endregion
37:
38:          #region Public Properties
39:
40:          /// <summary>
41:          /// The singleton instance
42:          /// </summary>
43:          public static Mediator Instance
44:          {
45:              get
46:              {
47:                  return instance;
48:              }
49:          }
50:
51:          #endregion
52:
53:          #region Public Methods
54:          /// <summary>
55:          /// Registers a Colleague to a specific message
56:          /// </summary>
57:          /// <param name="callback">The callback to use
58:          /// when the message it seen</param>
59:          /// <param name="message">The message to
60:          /// register to</param>
61:          public void Register(Action<Object> callback,
62:              ViewModelMessages message)
63:          {
64:              internalList.AddValue(message, callback);
65:          }
66:
67:
68:          /// <summary>
69:          /// Notify all colleagues that are registered to the
70:          /// specific message
71:          /// </summary>
72:          /// <param name="message">The message for the notify by</param>
73:          /// <param name="args">The arguments for the message</param>
74:          public void NotifyColleagues(ViewModelMessages message,
75:              object args)
76:          {
77:              if (internalList.ContainsKey(message))
78:              {
79:                  //forward the message to all listeners
80:                  foreach (Action<object> callback in
81:                      internalList[message])
82:                          callback(args);
83:              }
84:          }
85:          #endregion
86:
87:      }
88:  }

So how does this work, well let's see a ViewModel that sends a message via the mediator, notice the NotifyColleagues(..) method usage below.

 1:  using System;
 2:  using System.Collections.Generic;
 3:  using System.ComponentModel;
 4:  using System.Linq;
 5:  using System.Text;
 6:  using System.Windows.Input;
 7:  using System.Windows.Data;
 8:
 9:
10:  namespace MediatorDemo
11:  {
12:      public class WritersViewModel : ViewModelBase
13:      {
14:          private String writerText = null;
15:
16:
17:          public WritersViewModel()
18:          {
19:
20:
21:          }
22:
23:          public String WriterText
24:          {
25:              get { return writerText; }
26:              set
27:              {
28:                  writerText = value;
29:                  NotifyChanged("WriterText");
30:                  //alert others about this change
31:                  //via Mediator
32:                  Mediator.Instance.NotifyColleagues(
33:                      ViewModelMessages.UserWroteSomething,
34:                          writerText);
35:              }
36:          }
37:
38:      }
39:  }

And how about dealing with a message from the Mediator, this is fairly simple, though we do have to cast the results from Object to the Type we are expecting. This is necessary as the Mediator uses Action<Object> delegates as callbacks, where Object could be any Type of course.

 1:  using System;
 2:  using System.Collections.Generic;
 3:  using System.ComponentModel;
 4:  using System.Linq;
 5:  using System.Text;
 6:  using System.Windows.Input;
 7:
 8:
 9:  namespace MediatorDemo
10:  {
11:      public class ReadersViewModel : ViewModelBase
12:      {
13:          private String readText = String.Empty;
14:
15:
16:          public ReadersViewModel()
17:          {
18:              //register to the mediator for the
19:              //UserWroteSomething message
20:              Mediator.Instance.Register(
21:
22:                  //Callback delegate, when message is seen
23:                  (Object o) =>
24:                  {
25:                      ReadText = (String)o;
26:                  }, ViewModelMessages.UserWroteSomething);
27:          }
28:
29:
30:          public String ReadText
31:          {
32:              get { return readText; }
33:              set
34:              {
35:                  readText = value;
36:                  NotifyChanged("ReadText");
37:              }
38:          }
39:      }
40:  }

And that is really all there is to it. You now have a Writer ViewModel that will notify a totally disconnected Reader ViewModel about a change, via the messaging providing by the Mediator/Action<Object> delegate callbacks, that were registered for the message by the class that wants to do something based on the message being registered with the Mediator.

As I say, this is all thanks to Marlon, well done Marlon!

And here is a small demo app for you to play with.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here