Using the Mediator pattern in MVVM / WPF

Some instructions on how to use the Mediator design pattern as a means of allowing communication between ViewModel classes in your MVVM / WPF application.

In this example I use the Mediator as a means of communicating to the main window which view to display when the user clicks a button on either of the child views, View1 and View2.

Step 1: Create a new WPF application

mediator1

Step 2: Add event handling classes

EventArgs.cs

using System;

namespace MvvmSwitchViews
{
    public class EventArgs<T> : EventArgs
    {
        public EventArgs(T value)
        {
            Value = value;
        }

        public T Value { get; private set; }
    }
}

EventRaiser.cs

using System;

namespace MvvmSwitchViews
{
    public static class EventRaiser
    {
        public static void Raise(this EventHandler handler, object sender)
        {
            if (handler != null)
            {
                handler(sender, EventArgs.Empty);
            }
        }

        public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
        {
            if (handler != null)
            {
                handler(sender, new EventArgs<T>(value));
            }
        }

        public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
        {
            if (handler != null)
            {
                handler(sender, value);
            }
        }

        public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
        {
            if (handler != null)
            {
                handler(sender, value);
            }
        }
    }
}

RelayCommand.cs

using System;
using System.Windows.Input;

namespace MvvmSwitchViews
{
    public class RelayCommand<T> : ICommand
    {
        private readonly Predicate<T> _canExecute;
        private readonly Action<T> _execute;

        public RelayCommand(Action<T> execute)
            : this(execute, null)
        {
            _execute = execute;
        }

        public RelayCommand(Action<T> execute, Predicate<T> canExecute)
        {
            if (execute == null)
            {
                throw new ArgumentNullException("execute");
            }
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute((T)parameter);
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }

    public class RelayCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
            _execute = execute;
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
            {
                throw new ArgumentNullException("execute");
            }
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
        // associated views should be enabled whenever a command is invoked 
        public event EventHandler CanExecuteChanged
        {
            add
            {
                CommandManager.RequerySuggested += value;
                CanExecuteChangedInternal += value;
            }
            remove
            {
                CommandManager.RequerySuggested -= value;
                CanExecuteChangedInternal -= value;
            }
        }

        private event EventHandler CanExecuteChangedInternal;

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChangedInternal.Raise(this);
        }
    }
}

Step 3: Create the views

For demonstration purposes we add 2 x new views, each with a button that directs the user to another screen:

View1.xaml

<UserControl x:Class="MvvmSwitchViews.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MvvmSwitchViews"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:View1ViewModel />
    </UserControl.DataContext>
    
    <Grid>
        <Button 
            Content="Goto View 2"
            Command="{Binding GotoView2Command}"           
		    HorizontalAlignment="Center"               
		    Margin="10,10,0,0"
		    VerticalAlignment="Center"
		    Width="75">
        </Button>
    </Grid>
</UserControl>

View2.xaml

<UserControl x:Class="MvvmSwitchViews.View2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MvvmSwitchViews"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.DataContext>
        <local:View2ViewModel />
    </UserControl.DataContext>
    
    <Grid>
        <Button 
            Content="Goto View 1"
            Command="{Binding GotoView1Command}"           
		    HorizontalAlignment="Center"               
		    Margin="10,10,0,0"
		    VerticalAlignment="Center"
		    Width="75">
        </Button>
    </Grid>
</UserControl>

MainWindow.xaml

The MainWindow.xaml is updated as follows:

We use a DataTemplate and ContentControl to enable us to switch between views:

<Window x:Class="MvvmSwitchViews.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MvvmSwitchViews"  
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:View1ViewModel}">
            <local:View1/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:View2ViewModel}">
            <local:View2/>
        </DataTemplate>
    </Window.Resources>

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <ContentControl Content="{Binding CurrentView}" />
    </Grid>
</Window>

Step 4: Create the ViewModel classes

ViewModelBase.cs

using System.ComponentModel;

namespace MvvmSwitchViews
{
   public class ViewModelBase : INotifyPropertyChanged
   {
      public event PropertyChangedEventHandler PropertyChanged;
      protected void OnPropertyChanged(string propertyName)
      {

         var handler = PropertyChanged;
         if (handler != null)
         {
            handler(this, new PropertyChangedEventArgs(propertyName));
         }
      }
   }
}

View1ViewModel1.cs

using System;
using System.Windows.Input;

namespace MvvmSwitchViews
{
    public class View1ViewModel : ViewModelBase
    {
        private ICommand _gotoView2Command;

        public ICommand GotoView2Command
        {
            get
            {
                return _gotoView2Command ?? (_gotoView2Command = new RelayCommand(
                   x =>
                   {
                       Mediator.NotifyColleagues("ChangeView", false);
                   }));
            }
        }
    }
}

View2ViewModel.cs

using System;
using System.Windows.Input;

namespace MvvmSwitchViews
{
    public class View2ViewModel : ViewModelBase
    {
        private ICommand _gotoView1Command;

        public ICommand GotoView1Command
        {
            get
            {
                return _gotoView1Command ?? (_gotoView1Command = new RelayCommand(
                   x =>
                   {
                       Mediator.NotifyColleagues("ChangeView", true);
                   }));
            }
        }
    }
}

Update the ViewModel class for the Main Window ViewModel as well:

MainWindowViewModel.cs

using System.Windows.Input;

namespace MvvmSwitchViews
{
    public class MainWindowViewModel : ViewModelBase
    {
        private object _view1 = new View1();
        private object _view2 = new View2();
        private object _currentView;     

        public MainWindowViewModel()
        {
            _currentView = _view1;
            Mediator.Register("ChangeView", OnChangeView);
        }

        public object CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                _currentView = value;
                OnPropertyChanged("CurrentView");
            } 
        }

        public void OnChangeView(object show)
        {
            bool showView1 = (bool)show;
            CurrentView = showView1 ? _view1 : _view2;
        }
    }
}

Step 5: Create the Mediator static class

Mediator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MvvmSwitchViews
{
    static public class Mediator
    {
        static IDictionary<string, List<Action<object>>> pl_dict = new Dictionary<string, List<Action<object>>>();

        static public void Register(string token, Action<object> callback)
        {
            if (!pl_dict.ContainsKey(token))
            {
                var list = new List<Action<object>>();
                list.Add(callback);
                pl_dict.Add(token, list);
            }
            else
            {
                bool found = false;
                foreach (var item in pl_dict[token])
                    if (item.Method.ToString() == callback.Method.ToString())
                        found = true;
                if (!found)
                    pl_dict[token].Add(callback);
            } 
        }

        static public void Unregister(string token, Action<object> callback)
        {
            if (pl_dict.ContainsKey(token))
                pl_dict[token].Remove(callback);
        }

        static public void NotifyColleagues(string token, object args)
        {
            if (pl_dict.ContainsKey(token))
                foreach (var callback in pl_dict[token])
                    callback(args);
        }
    }
}

Step 6: Try the code

So in this example, the MainWindowViewModel class subscribes to the “ChangeView” events that are initiated in the View1ViewModel and View2ViewModel classes that are bound to View1 and View2 respectively.

mediator2

So when the user clicks the button in the first view, the Mediator class notifies it’s listeners that this event has happened, telling it not to switch to View1 but to View2 instead:

mediator3


Leave a Reply