Click here to Skip to main content
11,718,289 members (88,946 online)
Click here to Skip to main content

Tagged as

MVVM Mediator Pattern

, 9 Apr 2009 CPOL 39.2K 31
Rate this:
Please Sign up or sign in to vote.
About 1 year ago a good friend of mine Marlon Grech wrote a lovely article on MVC + M. You can read all about Marlons great article over at http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/ Essentially what Marlon did was create a message system to allow disparate MVC c

About 1 year ago a good friend of mine Marlon Grech wrote a lovely article on MVC + M. You can read all about Marlons great article over at http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/

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 Marlons original post was using the MVC pattern along with an additional class called the Mediator whom 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

Lets have a look at some code:

1st a helper class (lifted from Marlons 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 Marlons original code he used Strings, where as 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 registed 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 lets 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

You may also be interested in...

Comments and Discussions

 
QuestionWhere's the code? Pin
Camuvingian17-Sep-13 5:00
memberCamuvingian17-Sep-13 5:00 
GeneralMy vote of 5 Pin
Member 844408910-Sep-13 1:00
memberMember 844408910-Sep-13 1:00 
GeneralMy vote of 5 Pin
vinod.sankuthodi5-Aug-12 2:28
membervinod.sankuthodi5-Aug-12 2:28 
GeneralMy vote of 5 Pin
Dean Oliver5-Feb-12 7:10
memberDean Oliver5-Feb-12 7:10 
Questionimplementing singleton thread safe in C# Pin
aifarfa18-Dec-11 23:23
memberaifarfa18-Dec-11 23:23 
AnswerRe: implementing singleton thread safe in C# Pin
Sacha Barber19-Dec-11 2:50
mvpSacha Barber19-Dec-11 2:50 
QuestionTwo Constructors Pin
abbyjeet20-Jun-11 5:08
memberabbyjeet20-Jun-11 5:08 
AnswerRe: Two Constructors Pin
Sacha Barber21-Jun-11 2:26
mvpSacha Barber21-Jun-11 2:26 
GeneralRe: Two Constructors Pin
abbyjeet21-Jun-11 6:51
memberabbyjeet21-Jun-11 6:51 
GeneralRe: Two Constructors Pin
Sacha Barber21-Jun-11 6:59
mvpSacha Barber21-Jun-11 6:59 
GeneralThanks Re: Two Constructors Pin
abbyjeet21-Jun-11 8:32
memberabbyjeet21-Jun-11 8:32 
Thanks Sacha!
That was quick Smile | :)
GeneralRe: Thanks Re: Two Constructors Pin
Sacha Barber21-Jun-11 9:55
mvpSacha Barber21-Jun-11 9:55 
GeneralRe: Thanks Re: Two Constructors Pin
abbyjeet21-Jun-11 18:18
memberabbyjeet21-Jun-11 18:18 
GeneralRe: Thanks Re: Two Constructors Pin
Sacha Barber21-Jun-11 21:16
mvpSacha Barber21-Jun-11 21:16 
GeneralYay. Pin
Nedim Sabic14-Sep-09 8:53
memberNedim Sabic14-Sep-09 8:53 
GeneralRe: Yay. Pin
Sacha Barber14-Sep-09 9:37
mvpSacha Barber14-Sep-09 9:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150901.1 | Last Updated 9 Apr 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid