Click here to Skip to main content
Click here to Skip to main content

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

, 21 Mar 2014 MIT
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Pete O'Hanlon
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.
 
I am not the Stig, but I do wish I had Lotus Tuned Suspension.
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMemory Leak PinmemberMike Marynowski31-May-11 11:36 
General[My vote of 1] ...but is definitely harder than the article points out PinmemberwinSharp9317-Sep-10 9:51 
Your are missing some very important points:
 
First, according to the documentation of AddHandler "propertyName" (when registering a property change handler) can also be String.Empty "to indicate "any property".".
Your class does not handle this case.
 
Second, your code does not take care for which instance a type has registered a handler:
Bar bar = new Bar();
Foo foo = new Foo(bar);
 
Baz baz = new Baz();
PropertyChangedEventManager.AddListener(baz, this, "Property");
 
baz.Property = "Hello";
baz.Property = "World";
 
GC.KeepAlive(foo);
 
/*Output:
* Foo: Property Property of SilverlightApplication1.Baz changed
* Foo: Property Property of SilverlightApplication1.Baz changed
*/
 
//...

public sealed class Foo
    : NotifyPropertyChanged, IWeakEventListener
{
    public Foo(Bar bar)
    {
        PropertyChangedEventManager.AddListener(bar, this, "Property");
    }
 
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        Debug.WriteLine("Foo: Property {0} of {1} changed", ((PropertyChangedEventArgs) e).PropertyName, sender);
        return true;
    }
}
 
public abstract class NotifyPropertyChanged
    : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    public void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
 
public sealed class Bar
    : NotifyPropertyChanged
{
    private string _property;
 
    public string Property
    {
        get
        {
            return this._property;
        }
        set
        {
            if (this.Property == value)
                return;
 
            this._property = value;
            this.OnPropertyChanged("Property");
        }
    }
 
    public override string ToString()
    {
        return String.Format("Bar ({0})", this.Property);
    }
}
public sealed class Baz
    : NotifyPropertyChanged
{
    private string _property;
 
    public string Property
    {
        get
        {
            return this._property;
        }
        set
        {
            if (this.Property == value)
                return;
 
            this._property = value;
            this.OnPropertyChanged("Property");
        }
    }
}
 
The implementation is too much simplified which makes it not really helpful and debugging a pain if you expect it to behave at least a bit similar to the WPF version.
GeneralFound a bug PinmemberPontusM5-Feb-10 4:13 
GeneralRe: Found a bug PinmvpPete O'Hanlon5-Feb-10 4:16 

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.1411028.1 | Last Updated 21 Mar 2014
Article Copyright 2009 by Pete O'Hanlon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid