A reader recently sent me a message requesting further clarification on how one might use the generic IWeakEventListener implementation I recently wrote about (note to self: I need to disable the auto-comment disabling feature on this site).

The Scenario

Let’s say we have a child/parent pair of view models which serve as abstractions of a child/pair pair of views. The child view model mediates between a single item model and the child view, whereas the parent view model mediates between a collection-based item model and the parent view. Naturally, the parent view is going to have some type of ItemsControl living on it, which will, in turn, be generating one or more child view instances.

Now, let’s further assume that a requirement exists that compels us to subscribe to an event made available by the parent view model from somewhere on the child view model’s level. Thus, in this case, the parent view model is the publisher or source and the subscribing element exposed by the child view model is the subscriber or listener. For the purposes of this article, let’s say that it is an ICommand which is exposed by the child view model needs to subscribe to the parent view model’s event, for whatever reason.

Most of the time, in a normal .NET application, this kind of requirement isn’t a big deal. That’s because, unless we’re doing something wrong, our design should be such that the proper mechanism is in place (e.g. IDisposable, etc.) which will ensure that the child object will always be cognizant of when it is no longer needed and capable of responding to that.

The reason why the notion of weak event management suddenly becomes more prevalent or even essential when working with WPF is because of the very fact that WPF eschews various paradigms and practices followed religiously by other parts of the .NET Framework. One implication from this is the greater likelihood of a scenario occurring where our subscriber cannot and will not be made aware of when it should unregister from the events it has subscribed to.

To get around this problem, we make use of the weak event pattern. We already made a generic weak event listener in the previous article, so let’s show how to fully use it.

Using Weak Events

Our subscriber class (that which attaches to the published events and thus holds the event handlers that respond to them) is what will contain an instance of our generic weak event listener. If we follow the scenario shown above, we have a command on the child view model which needs to attach to an event. Let’s assume the parent view model implements an interface named INotifyAboutSomething which indicates its capability of publishing an event named SomethingHappened which, when fired, itself indicates a change in…something.

A Weak Event Manager

Before we can use an instance of our generic weak event listener in the definition of our command, however, we need to create a WeakEventManager for the particular event we want to handle. The event in question is the SomethingHappened event, so let’s define a class named SomethingHappenedEventManager (and just a note, none of the code, in the form that it is being made available, has been tested, although the code it originates from has been).

SomethingHappenedEventManager.cs
/// <summary>
/// Provides a <see cref="WeakEventManager"/> implementation so that you can use the weak event listener
/// pattern to attach listeners for the <see cref="INotifyAboutSomething.SomethingHappened"/> event.
/// </summary>
public sealed class SomethingHappenedEventManager : WeakEventManager
{
    /// <summary>
    /// Gets the currently initialized instance of this specific manager type; if one does not already exist,
    /// then this will create and register a new instance.
    /// </summary>
    private static SomethingHappenedEventManager CurrentManager
    {
        get
        {
            Type managerType = typeof(SomethingHappenedEventManager);

            SomethingHappenedEventManager currentManager
                = (SomethingHappenedEventManager) GetCurrentManager(managerType);

            if (null == currentManager)
            {
                currentManager = new SomethingHappenedEventManager();

                SetCurrentManager(managerType, currentManager);
            }

            return currentManager;
        }
    }

    /// <summary>
    /// Adds the specified listener to the <see cref="INotifyAboutSomething.SomethingHappened"/> event of the
    /// specified source.
    /// </summary>
    /// <param name="source">The <see cref="INotifyAboutSomething"/> object with the event.</param>
    /// <param name="listener">The <see cref="IWeakEventListener"/> object to add as a listener.</param>
    public static void AddListener(
        [NotNull]INotifyAboutSomething source, [NotNull]IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    /// <summary>
    /// Removes the specified listener from the <see cref="INotifyAboutSomething.SomethingHappened"/> event
    /// of the specified source.
    /// </summary>
    /// <param name="source">The <see cref="INotifyAboutSomething"/> object with the event.</param>
    /// <param name="listener">The <see cref="IWeakEventListener"/> object to remove.</param>
    public static void RemoveListener(INotifyAboutSomething source, [NotNull]IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    /// <inheritdoc/>
    protected override void StartListening(object source)
    {
        INotifyAboutSomething publisher = (INotifyAboutSomething) source;

        publisher.SomethingHappened += HandleSomethingHappened;
    }

    /// <inheritdoc/>
    protected override void StopListening(object source)
    {
        INotifyAboutSomething publisher = (INotifyAboutSomething) source;

        publisher.SomethingHappened -= HandleSomethingHappened;
    }

    /// <summary>
    /// Handles a change in something.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="eventArgs">
    /// The <see cref="SomethingHappenedEventArgs"/> instance containing the event data.
    /// </param>
    private void HandleSomethingHappened(object sender, SomethingHappenedEventArgs eventArgs)
    {
        DeliverEvent(sender, eventArgs);
    }
}

You can create a generic weak event manager if you wish as well, however, for reasons I pointed out in my previous article on the subject, it may not be in your best interest to do so.

Using It All Together

With the weak event manager defined, here’s a skeleton of our command then, showing the usage of our generic weak event listener and the weak event manager we just defined.

TheCommand.cs
/// <summary>
/// Provides a command that does something in response to something.
/// </summary>
public class TheCommand : ICommand
{
    private readonly WeakEventListener<SomethingHappenedEventArgs> _somethingHappenedListener;

    /// <summary>
    /// Initializes a new instance of the <see cref="TheCommand"/> class.
    /// </summary>
    /// <param name="publisher">
    /// The publisher of the event this command is responding to in some fashion.
    /// </param>
    public TheCommand([NotNull]INotifySomethingHappened publisher)
    {
        _somethingHappenedListener = new WeakEventListener<SomethingHappenedEventArgs>(OnSomethingHappened);

        SomethingHappenedEventManager.AddListener(publisher, _somethingHappenedListener);
    }

    /// <summary>
    /// Responds to something happening by...doing something else.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="eventArgs">
    /// The <see cref="SomethingHappenedEventArgs"/> instance containing the event data.
    /// </param>
    private void OnPoolChanged(object sender, SomethingHappenedEventArgs eventArgs)
    {
        // ...
    }
}

The magic above occurs when we make a call to the static SomethingHappenedEventManager.AddListener method, which is going to complete the registration of our weak event listener. Note that there is also a RemoveListener method, which, if possible, we can call in order to be as tidy as possible, but this is not necessary, we’re dealing with weak references here. Although, that wouldn’t excuse us from omitting a call to RemoveListener if a state does indeed exist in which we become aware that it is our time to go. Tidy, tidy!

The code above is obviously missing other parts required of a command, as that has nothing to do with this article (I’m just letting all you friendly copy-pasters out there know).

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.