Saturday, July 27, 2013

Communication Between Views in MVVM (Pub-Sub Pattern)


When you write WPF application you obviously use MVVM pattern. At some point you probably will need some communication between separated views. Well, there is a bunch of tools for this purposes -MVVM Light’s Messenger or Microsoft Prism’s EventAggregator, but today I'll show an alternative way by using Publish-Subscribe Pattern aka Observer.

Pub-Sub is a simple static class that will make your life easier with MVVM:
public delegate void PubSubEventHandler<T>(object sender, PubSubEventArgs<T> args);

public class PubSubEventArgs<T> : EventArgs
{
    public T Item { get; set; }

    public PubSubEventArgs(T item)
    {
        Item = item;
    }
}

public static class PubSub<T>
{
    private static Dictionary<string, PubSubEventHandler<T>> events = 
            new Dictionary<string, PubSubEventHandler<T>>();

    public static void AddEvent(string name, PubSubEventHandler<T> handler)
    {
        if (!events.ContainsKey(name))
            events.Add(name, handler);
    }
    public static void RaiseEvent(string name, object sender, PubSubEventArgs<T> args)
    {
        if (events.ContainsKey(name) && events[name] != null)
            events[name](sender, args);
    }
    public static void RegisterEvent(string name, PubSubEventHandler<T> handler)
    {
        if (events.ContainsKey(name))
            events[name] += handler;
    }
}
I created a simple application in order to show how to use Pub-Sub. We have WPF window with two different views: MyToggleButton and MyGrid. I want the Grid become Red when I press the Toggle Button and Blue when I release it. Don't forget these are separated views.
pub-sub sample aplication with MVVM

Let's see the code of MyToggleButtonViewModel:
public class MyToggleButtonViewModel : ViewModelBase
{
    public event PubSubEventHandler<object> ToggleButtonCheckedHandler;

    public MyToggleButtonViewModel()
    {
        PubSub<object>.AddEvent("ToggleButtonPressed", ToggleButtonCheckedHandler);
    }

    private bool isToggleButtonChecked;
    public bool IsToggleButtonChecked 
    { 
        get
        {
            return isToggleButtonChecked;
        }
        set
        {
            if (isToggleButtonChecked != value)
            {   
                isToggleButtonChecked = value;
                OnPropertyChanged("IsToggleButtonChecked");
                if (value)
                    PubSub<object>.RaiseEvent("ToggleButtonPressed", this, 
                                                new PubSubEventArgs<object>("Red"));
                else
                    PubSub<object>.RaiseEvent("ToggleButtonPressed", this, 
                                                new PubSubEventArgs<object>("Blue"));
            }
        }
    }        
}
As you can see we simply adding an event which we called "ToggleButtonPressed" and raising it with different parameters (Blue or Red). In MyGridViewModel we need to register the event and implement a handler:
public class MyGridViewModel : ViewModelBase
{
    public MyGridViewModel()
    {
        PubSub<object>.RegisterEvent("ToggleButtonPressed", ToggleButtonCheckedHandler);
    }

    private string backgroundColor;        
    public string BackgroundColor 
    { 
        get
        {
            return backgroundColor;
        }
        set
        {
            if (backgroundColor != value)
            {
                backgroundColor = value;
                OnPropertyChanged("BackgroundColor");
            }
        }
    }

    public void ToggleButtonCheckedHandler(object sender, PubSubEventArgs<object> args)
    {
        BackgroundColor = (string)args.Item;
    }
}
This is it! Download the source code (Visual Studio 2010 project).

2 comments:

  1. Why are you defining a generic PubSub only to use it with object?
    PubSub(object)

    ReplyDelete
  2. Hey nice tutorial, but your code gets the error:
    Error CS1503 Argument 2: cannot convert from 'n.PubSubEventArgs' to 'n.PubSubEventHandler'

    ReplyDelete