Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / WPF

NotifyParentObservableCollection (Part 2) monitors changes of its children properties

Rate me:
Please Sign up or sign in to vote.
4.85/5 (11 votes)
4 Feb 2011Eclipse2 min read 32.4K   370   18   5
ObservableCollection attaches to the PropertyChanged events of its children using the WeakEvents pattern, and raises the ChildPropertyChanged event accordingly. Replicates functionality of BindingList but without the overhead. Very useful when you need to update / select / arrange / move items.

Table of Contents

Weak Events

Implemented with strong event handlers, NotifyParentObservableCollection<T><t> did what it was supposed to do. However, my first article received a lot of negative feedback.

My collection was supposed to replace functionality offered by BindingList<T> (forwarding of INotifyPropertyChanged events of its children).

I was told that BindingList<T> attaches to PropertyChanged events through weak references (which is apparently not the case, but anyway), while my collection uses strong references. This is a potential memory leak, and the programmer using NotifyParentObservableCollection<T> would have to call Clear() to release those references.

OK, time to stop being lazy and implement the WeakEvents pattern.

I knew that MSFT has WeakEventManager, but just in case, I decided to surf the Web for the best WeakEvents implementation. To quote Nathan Nesbit[^]:

"There are many examples on the web for implementing some kind of weak listener - but interestingly, almost all of them have some flaw. In the end, we settled on using WeakEventManager and its associated IWeakEventListener interface."

All other solutions create an instance of the wrapper class attached to the source with a strong reference, and to the target with a weak reference. Every time the source event fires, the wrapper checks if the target is still alive. If the target is dead, the wrapper detaches from the source and can be garbage collected.

WeakEventWithWrapper.png

Unfortunately, this is a potential memory leak. If the source events never fire, thousands of wrappers can be leaked: Solving the Problem with Events: Weak Event Handlers[^], Weak Events in C#[^].

Looks like that the MSFT way makes most sense: create a single static event wrapper per event type, and clean up unused connections on the background thread.

I renamed my collection StrongNotifyParentObservableCollection<T> and re-implemented NotifyParentObservableCollection<T> (as well as the demo application) using WeakEventManager and IWeakEventListener. I wrote couple classes implementing WeakEventManager (ChildPropertyChangedEventManager and AnyPropertyChangedEventManager), and implemented IWeakEventListener in NotifyParentObservableCollection<T>, as well as in the ViewModel.

Voila, there is no strong references left anymore, everything is weak now! My NotifyParentObservableCollection<T> forwards children INPC events to interested listeners using WeakEventManager, just like BindingList<T> supposedly did.

Goal is achieved. Weak is strong. Chinese philosophy is right once again.

NotifyParentObservableCollection<T>

C#
private void AttachHandlers(IEnumerable items)
{
    if (items != null)
    {
        foreach (var item in items.OfType<inotifypropertychanged>())
        {
            AnyPropertyChangedEventManager.AddListener(item, this);
        }
    }
}

private void DetachHandlers(IEnumerable items)
{
    if (items != null)
    {
        foreach (var item in items.OfType<inotifypropertychanged>())
        {
            AnyPropertyChangedEventManager.RemoveListener(item, this);
        }
    }
}

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
    if (managerType == typeof(AnyPropertyChangedEventManager))
    {
        if (SupportsChangeNotifications)
            OnChildPropertyChanged(this, 
                                   sender,
                                   ((PropertyChangedEventArgs)e).PropertyName);
    }
    return true;
}

void OnChildPropertyChanged(object sender, object source, string property)
{
    var childPropertyChanged = ChildPropertyChanged;
    if (childPropertyChanged != null)
    {
        childPropertyChanged(sender, 
                             new ChildPropertyChangedEventArgs(source, property));
    }
}

AnyPropertyChangedEventManager

C#
public class AnyPropertyChangedEventManager : WeakEventManager
{
    private AnyPropertyChangedEventManager() { }

    public static void AddListener(INotifyPropertyChanged source, 
                                   IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(INotifyPropertyChanged source, 
                                      IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    protected override void StartListening(object source)
    {
        ((INotifyPropertyChanged)source).PropertyChanged += DeliverEvent;
    }

    protected override void StopListening(object source)
    {
        ((INotifyPropertyChanged)source).PropertyChanged -= DeliverEvent;
    }

    private static AnyPropertyChangedEventManager CurrentManager
    {
        get
        {
            var managerType = typeof(AnyPropertyChangedEventManager);
            var manager = (AnyPropertyChangedEventManager)
                                 GetCurrentManager(managerType);
            if (manager == null)
            {
                manager = new AnyPropertyChangedEventManager();
                SetCurrentManager(managerType, manager);
            }
            return manager;
        }
    }
}

ViewModel

C#
private NotifyParentObservableCollection<LookupItem> _selectors;
public NotifyParentObservableCollection<LookupItem> Selectors
{
    get { return _selectors; }
    private set
    {
        if (_selectors != null)
        {
            ChildPropertyChangedEventManager.RemoveListener(_selectors, this);
            CollectionChangedEventManager.RemoveListener(_selectors, this);
        }
        _selectors = value;
        if (_selectors != null)
        {
            ChildPropertyChangedEventManager.AddListener(_selectors, this);
            CollectionChangedEventManager.AddListener(_selectors, this);
        }
        OnPropertyChanged("Selectors");
    }
}

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
    if (managerType == typeof(CollectionChangedEventManager))
    {
        // Put all your CollectionChanged event handlers here
        if (sender == Selectors)
            OnSelectorsCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);
    }
    else if (managerType == typeof(ChildPropertyChangedEventManager))
    {
        // Put all your ChildPropertyChanged event handlers here
        if (sender == Selectors)
            OnSelectorsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
        else if (sender == Words)
            OnWordsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
    }
    else if (managerType == typeof(AnyPropertyChangedEventManager))
    {
        // Put all your PropertyChanged event handlers here
    }
    return true;
}

ChildPropertyChangedEventManager

C#
public class ChildPropertyChangedEventManager : WeakEventManager
{
    private ChildPropertyChangedEventManager() { }

    public static void AddListener(IChildPropertyChanged source, 
                                   IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(IChildPropertyChanged source, 
                                      IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    protected override void StartListening(object source)
    {
        ((IChildPropertyChanged)source).ChildPropertyChanged += DeliverEvent;
    }
 
    protected override void StopListening(object source)
    {
        ((IChildPropertyChanged)source).ChildPropertyChanged -= DeliverEvent;
    }

    private static ChildPropertyChangedEventManager CurrentManager
    {
        get
        {
            var managerType = typeof(ChildPropertyChangedEventManager);
            var manager = (ChildPropertyChangedEventManager)
                                        GetCurrentManager(managerType);
            if (manager == null)
            {
                manager = new ChildPropertyChangedEventManager();
                SetCurrentManager(managerType, manager);
            }
            return manager;
        }
    }
}

Revision History

  • January 23, 2011 - Created the article.

License

This article, along with any associated source code and files, is licensed under The Eclipse Public License 1.0


Written By
Software Developer (Senior) Liquidnet
United States United States
Michael is a software developer who still remembers punch cards, computers with 4 Kbytes RAM, and 3270s. His personal computers were Apple IIe, Commodore, and PC XT (with the whole 640 Kbytes RAM and 2 floppy drives!!!). Wow, that was a powerhouse.

Fast forward 32 years through FORTRAN, PL-I, Algol, Pascal, Prolog, LISP, C, Basic, Clipper, Assembly, FoxPro, DHTML, JavaScript, C++, you name it, to C# 4.0.

Of course, real men use machine code to write software, but what a difference a few years make! No more mallocs and callocs, GC magically collects unused objects, dynamic objects magically call IUnknown::QueryInterface, Reflection magically gives you metadata and even generates help files, WPF magically binds stuff together...

Read some of Michael's articles here.

BindingHub (a WPF component and a design pattern) [^].

Notifying parent of changes to children's properties [^].

Point-In-Time database (coming soon)

Composite Menus and other ItemsControls (coming soon)

Adorners framework (coming soon)

Drag-n-drop data transfer framework (coming soon)

Converters and MarkupExtensions (coming soon)

Download complete WPF library [^].

Comments and Discussions

 
GeneralMy vote of 5 Pin
wujiong8-Feb-11 20:59
wujiong8-Feb-11 20:59 
GeneralNice post Pin
Shahriar Iqbal Chowdhury/Galib5-Feb-11 19:54
professionalShahriar Iqbal Chowdhury/Galib5-Feb-11 19:54 
GeneralMy vote of 2 Pin
Anuj kumar jain4-Feb-11 19:52
Anuj kumar jain4-Feb-11 19:52 
GeneralMy vote of 5 Pin
SledgeHammer0123-Jan-11 10:57
SledgeHammer0123-Jan-11 10:57 
GeneralRe: My vote of 5 Pin
Michael Agroskin4-Feb-11 13:25
Michael Agroskin4-Feb-11 13:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.