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

It’s not that hard to add the missing bits to Silverlight

0.00/5 (No votes)
21 Mar 2014 1  
Adding some missing bits to Silverlight.

Introduction

Recently, my good friend Josh Smith announced that he was putting together a set of MVVM foundation classes. In typical Josh fashion, these classes are hyper useful and hyper clever, and represent some of the great classes he’s produced over the last couple of years to help with MVVM.

Most of these classes work straight out of the box in both WPF and Silverlight, but there is a fly in the ointment. Josh recently developed a class to observe objects that implement INotifyPropertyChanged and it’s seriously good; and it’s virtually totally useless in Silverlight due to Silverlight not supporting the underlying mechanism that is used in the monitoring: the PropertyChangedEventManager class. Now, in a lesser framework than .NET, I’d be worried – but .NET gives us so much freedom to add in the missing functionality, and I wouldn’t be a WPF Disciple if I didn’t like to tinker.

Before I go any further, I will say that there is an alternative implementation in the Silverlight Toolkit in the WeakEventListener. The problem with this class (I use the word problem advisedly here) is that it requires you to use Lambda Expressions. It also meant that the class that Josh put together would have to be modified, so I decided to see how hard it would be to put together an implementation of PropertyChangedEventManager that works in Silverlight.

As I was working to a well defined feature set (i.e., I only had to work with the functionality in Josh’s class), I didn’t have to recreate the total functionality in the underlying framework classes. I did want it to work with the weak event pattern, however, so without further ado – here’s the functionality in Silverlight:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;

namespace System.Windows
{
  /// <summary>
  /// Provides an implementation so that you can use the
  /// "weak event listener" pattern to attach listeners
  /// for the <see cref="PropertyChanged" /> event.
  /// </summary>
  public class PropertyChangedEventManager
  {
    #region Members
    private Dictionary<string, List<WeakReference>> _list;
    private static object SyncLock = new object();
    private static PropertyChangedEventManager _manager = null;
    #endregion

    #region Public methods
    /// <summary>
    /// Adds the specified listener to the list of listeners on the specified source.
    /// </summary>
    /// <param name="source">The object with the event.</param>
    /// <param name="listener">The object to add as a listener.</param>
    /// <param name="propertyName">The name of the property that exists on
    /// source upon which to listen for changes.</param>
    public static void AddListener(INotifyPropertyChanged source,
      IWeakEventListener listener,
      string propertyName)
    {
      Instance.PrivateAddListener(source, listener, propertyName);
    }

    /// <summary>
    /// Removes the specified listener from the list of listeners on the
    /// specified source.
    /// </summary>
    /// <param name="source">The object with the event.</param>
    /// <param name="listener">The object to remove as a listener.</param>
    /// <param name="propertyName">The name of the property that exists
    /// on source upon which to listen for changes.</param>
    public static void RemoveListener(INotifyPropertyChanged source,
      IWeakEventListener listener,
      string propertyName)
    {
      Instance.PrivateRemoveListener(source, listener, propertyName);
    }
    #endregion

    /// <summary>
    /// Get the current instance of <see cref="PropertyChangedEventManager"/>
    /// </summary>
    private static PropertyChangedEventManager Instance
    {
      get
      {
        if (_manager == null)
          _manager = new PropertyChangedEventManager();
        return _manager;
      }
    }

    /// <summary>
    /// Begin listening for the <see cref="PropertyChanged"/> event on
    /// the provided source.
    /// </summary>
    /// <param name="source">The object on which to start listening
    /// for <see cref="PropertyChanged"/>.</param>
    private void StartListening(INotifyPropertyChanged source)
    {
      source.PropertyChanged += new PropertyChangedEventHandler(this.PropertyChanged);
    }

    /// <summary>
    /// Stop listening for the <see cref="PropertyChanged"/> event on the
    /// provided source.
    /// </summary>
    /// <param name="source">The object on which to start listening for
    /// <see cref="PropertyChanged"/>.</param>
    private void StopListening(INotifyPropertyChanged source)
    {
      source.PropertyChanged -= new PropertyChangedEventHandler(this.PropertyChanged);
    }

    /// <summary>
    /// The method that handles the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="args">A <see cref="PropertyChangedEventArgs"/> that
    /// contains the event data.</param>
    private void PropertyChanged(object sender, PropertyChangedEventArgs args)
    {
      List<WeakReference> list = _list[args.PropertyName];
      if (list != null)
      {
        // We have the listeners. Deal with them
        foreach (WeakReference item in list)
        {
          IWeakEventListener eventItem = item.Target as IWeakEventListener;
          if (eventItem != null && item.IsAlive)
          {
            eventItem.ReceiveWeakEvent(this.GetType(), sender, args);
          }
        }
      }
    }

    /// <summary>
    /// Private method to add the specified listener to the list of listeners
    /// on the specified source.
    /// </summary>
    /// <param name="source">The object with the event.</param>
    /// <param name="listener">The object to add as a listener.</param>
    /// <param name="propertyName">The name of the property that exists
    /// on source upon which to listen for changes.</param>
    private void PrivateAddListener(INotifyPropertyChanged source,
      IWeakEventListener listener,
      string propertyName)
    {
      if (_list == null)
      {
        _list = new Dictionary<string, List<WeakReference>>();
      }

      lock (SyncLock)
      {
        WeakReference reference = new WeakReference(listener);
        if (_list.ContainsKey(propertyName))
        {
          _list[propertyName].Add(reference);
        }
        else
        {
          List<WeakReference> list = new List<WeakReference>();
          list.Add(reference);
          _list.Add(propertyName, list);
        }
        // Now, start listening to source
        StartListening(source);
      }
    }

    /// <summary>
    /// Private method to remove the specified listener from the list of listeners
    /// on the specified source.
    /// </summary>
    /// <param name="source">The object with the event.</param>
    /// <param name="listener">The object to remove as a listener.</param>
    /// <param name="propertyName">The name of the property that exists on
    /// source upon which to listen for changes.</param>
    private void PrivateRemoveListener(INotifyPropertyChanged source,
      IWeakEventListener listener,
      string propertyName)
    {
      if (_list != null)
      {
        lock (SyncLock)
        {
          if (_list.ContainsKey(propertyName))
          {
            // Stop responding to changes
            StopListening(source);
            // Remove the item from the list.
            WeakReference reference = null;
            foreach (WeakReference item in _list[propertyName])
            {
              if (item.Target.Equals(listener))
              {
                reference = item;
              }
            }
            if (reference != null)
            {
              _list[propertyName].Remove(reference);
            }
          }
        }
      }
    }
  }
}

The only thing that’s missing is the weak event listener interface (IWeakEventListener):

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace System.Windows
{
  /// <summary>
  /// Provides event listening support for classes that expect to receive events
  /// through the WeakEvent pattern and a WeakEventManager.
  /// </summary>
  public interface IWeakEventListener
  {
    /// <summary>
    /// Receives events from the centralized event manager.
    /// </summary>
    /// <param name="managerType">The type of the
    ///    WeakEventManager calling this method.</param>
    /// <param name="sender">Object that originated the event.</param>
    /// <param name="e">Event data.</param>
    /// <returns>true if the listener handled the event. It is considered an error by the
    /// WeakEventManager handling in WPF to register a listener for an event that the
    /// listener does not handle. Regardless, the method should return false if it receives
    /// an event that it does not recognize or handle.
    /// </returns>
    bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
  }
}

That’s it – that’s all you need to add in to add the “missing” functionality. Simple, isn’t it?

You can download the source here. Note that you’ll have to rename the file from .doc to .zip before you can decompress it.

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