Diederik Krols

The XAML Brewer

A core MVVM framework for WinRT

This article presents a core MVVM framework for building Metro applications on the WinRT runtime.

With the WinRT runtime, Microsoft launched its fourth XAML-based platform - after WPf, Silverlight, and the Windows Phone. It's the fourth time they strongly advice development teams all over the world to get the maximum out of the power of XAML bindings, hence use the MVVM pattern in their applications. Strangely enough it's also the fourth time that they ship a runtime without the basic support for that same pattern. So it's up to the developers community to fill the gap.

The gap will be filled rapidly. While Microsoft Patterns & Practice's Prism team is still hesitating, Laurent Bugnion with his MVVM Light Toolkit  and Rob Eisenberg with Caliburn Micro are definitely going for a WinRT version of their popular frameworks. But we're not going to sit and wait for these frameworks, are we? We want to start developing MVVM applications (in Metro-speak they're called 'apps') here and now.

In my ever humble opinion, the following three are the core requirements for using MVVM in an application :

  1. Raising the PropertyChanged event should be easy and typesafe.
  2. Defining a bindable command in a ViewModel should be easy and typesafe.
  3. Messaging between components should be easy and typesafe, and not create memory leaks.

 

I gleaned a core MVVM-framework with just three files - one for each requirement:

The namespace is Mvvm, so you can start each source code file with 'using Mvvm;'. Your manager will be impressed.Wink

Change Notification


Most frameworks provide a base class that implements INotifyPropertyChanged. I'm a bit reluctant to use a scarse resource like inheritance- for such a triviality like just raising a standard event. Extension methods are much more flexible:

namespace Mvvm
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using Windows.UI.Xaml.Data;

    /// <summary>
    /// Generic extension methods used by the framework.
    /// </summary>
    public static class ExtensionMethods
    {
        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        public static void Raise<T, P>(this PropertyChangedEventHandler pc, T source, Expression<Func<T, P>> pe)
        {
            if (pc != null)
            {
                pc.Invoke(source,
                    new PropertyChangedEventArgs(((MemberExpression)pe.Body).Member.Name));
            }
        }

        /// <summary>
        /// Raises the PropertyChanged event for all properties.
        /// </summary>
        public static void RaiseAll<T>(this PropertyChangedEventHandler pc, T source)
        {
            if (pc != null)
            {
                pc.Invoke(source, new PropertyChangedEventArgs(string.Empty));
            }
        }
    }
}


Here's how a typical property looks like in a viewmodel or a model that implements INotifyPropertyChanged. Broadcasting a property value change is easy, ànd you get Intellisense support:

private string message

public string Message
{
    get { return this.message; }
    set
    {
        this.message = value;
        this.PropertyChanged.Raise(this, o => o.Message);
    }
}

It also definitely makes a good candidate for a code snippet...

Command binding


Most frameworks have an implementation of ICommand to be used specifically in viewmodels. Microsoft's RoutedCommand makes no sense there. Caliburn uses a radically different paradigm based on triggers and actions - the result is the same but it's further away from MVVM. The ICommand implementations in Prism and MVVM Light Toolkit have a constructor with just delegates for the Executed and CanExecute members. I prefer the RelayCommand implementation of the MVVM Light Toolkit, but I also prefer to call it DelegateCommand (sorry, Laurent) - the Prism terminology.

It's easy to use and it comes with a generic version:

namespace Mvvm
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    using Windows.UI.Xaml.Input;
    using EventHandler = Windows.UI.Xaml.EventHandler;

    public class DelegateCommand : ICommand
    {
        private readonly Action _execute;

        private readonly Func<bool> _canExecute;

        /// <summary>
        /// Initializes a new instance of the DelegateCommand class that 
        /// can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
        public DelegateCommand(Action execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the DelegateCommand class.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        /// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
        public DelegateCommand(Action execute, Func<bool> canExecute)
        {
            if (execute == null)
            {
                throw new ArgumentNullException("execute");
            }

            _execute = execute;
            _canExecute = canExecute;
        }

        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Raises the <see cref="CanExecuteChanged" /> event.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">This parameter will always be ignored.</param>
        /// <returns>true if this command can be executed; otherwise, false.</returns>
        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked. 
        /// </summary>
        /// <param name="parameter">This parameter will always be ignored.</param>
        public void Execute(object parameter)
        {
            if (CanExecute(parameter))
            {
                _execute();
            }
        }
    }
}

Here's how to use the DelegateCommand. The ViewModel defines a command by providing just delegates, and provides implementations:

public ICommand UpdateTextCommand
{
    get
    {
        return new DelegateCommand(this.UpdateTextCommand_Executed);
    }
}

private void UpdateTextCommand_Executed()
{
    // Command Logic
}

 

The View can bind to that command in XAML, no code-behind is needed:

<Button Content="Update Text" Command="{Binding UpdateTextCommand}" />

Event Aggregation


In an application that consists of different loosely-coupled components that need to publish and subscribe to events, it's not a good idea to rely on regular .NET events. More info can be found here. A so-called event aggregator is a much better solution. Prism's event aggregator may be the most powerful on the market, but the one from Caliburn Micro is easier to extract out of its framework. I just added the singleton pattern because I don't want to introduce an Inversion-of-Control container and bootstrappers in my three-file-framework.

Here's the full code of the Event Aggregator:

namespace Mvvm
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    ///   A marker interface for classes that subscribe to messages.
    /// </summary>
    public interface IHandle { }

    /// <summary>
    ///   Denotes a class which can handle a particular type of message.
    /// </summary>
    /// <typeparam name = "TMessage">The type of message to handle.</typeparam>
    public interface IHandle<TMessage> : IHandle
    {
        /// <summary>
        ///   Handles the message.
        /// </summary>
        /// <param name = "message">The message.</param>
        void Handle(TMessage message);
    }

    /// <summary>
    ///   Enables loosely-coupled publication of and subscription to events.
    /// </summary>
    public interface IEventAggregator
    {
        /// <summary>
        ///   Gets or sets the default publication thread marshaller.
        /// </summary>
        /// <value>
        ///   The default publication thread marshaller.
        /// </value>
        Action<System.Action> PublicationThreadMarshaller { get; set; }

        /// <summary>
        ///   Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
        /// </summary>
        /// <param name = "instance">The instance to subscribe for event publication.</param>
        void Subscribe(object instance);

        /// <summary>
        ///   Unsubscribes the instance from all events.
        /// </summary>
        /// <param name = "instance">The instance to unsubscribe.</param>
        void Unsubscribe(object instance);

        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <remarks>
        ///   Uses the default thread marshaller during publication.
        /// </remarks>
        void Publish(object message);

        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
        void Publish(object message, Action<System.Action> marshal);
    }

    /// <summary>
    ///   Enables loosely-coupled publication of and subscription to events.
    /// </summary>
    public class EventAggregator : IEventAggregator
    {
        /// <summary>
        ///   The default thread marshaller used for publication;
        /// </summary>
        public static Action<System.Action> DefaultPublicationThreadMarshaller = action => action();

        private static IEventAggregator instance;
        
        readonly List<Handler> handlers = new List<Handler>();

        /// <summary>
        ///   Initializes a new instance of the <see cref = "EventAggregator" /> class.
        /// </summary>
        public EventAggregator()
        {
            PublicationThreadMarshaller = DefaultPublicationThreadMarshaller;
        }

        /// <summary>
        /// Gets the singleton instance.
        /// </summary>
        public static IEventAggregator Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new EventAggregator();
                }
                return instance;
            }
        }

        /// <summary>
        ///   Gets or sets the default publication thread marshaller.
        /// </summary>
        /// <value>
        ///   The default publication thread marshaller.
        /// </value>
        public Action<System.Action> PublicationThreadMarshaller { get; set; }

        /// <summary>
        ///   Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
        /// </summary>
        /// <param name = "instance">The instance to subscribe for event publication.</param>
        public virtual void Subscribe(object instance)
        {
            lock (handlers)
            {
                if (handlers.Any(x => x.Matches(instance)))
                    return;

                handlers.Add(new Handler(instance));
            }
        }

        /// <summary>
        ///   Unsubscribes the instance from all events.
        /// </summary>
        /// <param name = "instance">The instance to unsubscribe.</param>
        public virtual void Unsubscribe(object instance)
        {
            lock (handlers)
            {
                var found = handlers.FirstOrDefault(x => x.Matches(instance));

                if (found != null)
                    handlers.Remove(found);
            }
        }

        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <remarks>
        ///   Does not marshall the the publication to any special thread by default.
        /// </remarks>
        public virtual void Publish(object message)
        {
            Publish(message, PublicationThreadMarshaller);
        }

        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
        public virtual void Publish(object message, Action<System.Action> marshal)
        {
            Handler[] toNotify;
            lock (handlers)
                toNotify = handlers.ToArray();

            marshal(() =>
            {
                var messageType = message.GetType();

                var dead = toNotify
                    .Where(handler => !handler.Handle(messageType, message))
                    .ToList();

                if (dead.Any())
                {
                    lock (handlers)
                    {
                        dead.Apply(x => handlers.Remove(x));
                    }
                }
            });
        }

        protected class Handler
        {
            readonly WeakReference reference;
            readonly Dictionary<TypeInfo, MethodInfo> supportedHandlers = new Dictionary<TypeInfo, MethodInfo>();

            public Handler(object handler)
            {
                reference = new WeakReference(handler);

                var handlerInfo = typeof(IHandle).GetTypeInfo();
                var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces
                    .Where(x => handlerInfo.IsAssignableFrom(x.GetTypeInfo()) && x.IsGenericType);

                foreach (var @interface in interfaces)
                {
                    var type = @interface.GenericTypeArguments[0];
                    var method = @interface.GetTypeInfo().DeclaredMethods.First(x => x.Name == "Handle");
                    supportedHandlers[type.GetTypeInfo()] = method;
                }
            }

            public bool Matches(object instance)
            {
                return reference.Target == instance;
            }

            public bool Handle(Type messageType, object message)
            {
                var target = reference.Target;
                if (target == null)
                    return false;

                var typeInfo = messageType.GetTypeInfo();

                foreach (var pair in supportedHandlers)
                {
                    if (pair.Key.IsAssignableFrom(typeInfo))
                    {
                        pair.Value.Invoke(target, new[] { message });
                        return true;
                    }
                }

                return true;
            }
        }
    }

    /// <summary>
    /// Generic extension methods used by the framework.
    /// </summary>
    public static class EventAggregatorExtensionMethods
    {
        /// <summary>
        /// Applies the action to each element in the list.
        /// </summary>
        /// <typeparam name="T">The enumerable item's type.</typeparam>
        /// <param name="enumerable">The elements to enumerate.</param>
        /// <param name="action">The action to apply to each item in the list.</param>
        public static void Apply<T>(this IEnumerable<T> enumerable, Action<T> action)
        {
            foreach (var item in enumerable)
                action(item);
        }
    }
}

Here's how to use it. Please note that I'm just passing messages of type String, you might want to define your own event payload classes. A publisher just needs to get a reference to the central even handler, and publish its message:

EventAggregator.Instance.Publish(this.Message);

The subscriber registers himself to the same event aggregator. He specifies the type of message he's interested in by implementing the IHandle<T> interface:

class SubscriberViewModel : INotifyPropertyChanged, IHandle<string>
{ 
    public SubscriberViewModel()
    {
        // Subscribe
        EventAggregator.Instance.Subscribe(this);
    }

    public void Handle(string message)
    {
        // Event handling comes here
    }
}

 Publisher and subscriber never have a reference to each other, while the event aggregator uses weak references. This results in a memory-leak free pattern.

Sample


I built a small and simple application (oops, I did it again: app) to test drive the framework. Here's how it looks like:

On the left side of the screen, there's a text balloon and a button. These belong to the publisher. Clicking the button updates the Message property and via the change notification extension methods also the GUI. It sends the new value as message through the event aggregator.  The text balloon in the middle belongs to a subscriber that is instantiated using a view-first approach: the view instantiates its viewmodel. Typically this is done by defining its data context in XAML - but that does not seem to work yet, so I needed some code behind. The text balloon on the right belongs to another subscriber. This one is instantiated using the viewmodel-first approach. When the datacontext of a control is set to a viewmodel, the control decides what template to load. This is typically done with typed data templates in WPF, or with a value converter in Silverlight. Since typed data templates are not available in Metro, I used the Silverlight approach. [Note: The IValueConverter interface in Metro is different from its predecessors.] Both subscribers receive the message through the event aggregator subscription, and use the change notification extensions methods it to update their own Message property, populating the text balloon.

Source Code


Here's the full source code. It requires VS 2011 Developer Preview: U2UConsult.WinRT.MVVM.zip (205,86 kb)

Enjoy!

 

Comments are closed