public
Last active

  • Download Gist
ChangeListener.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
using System;
using System.Collections.Specialized;
using System.ComponentModel;
 
namespace ThomasJaworski.ComponentModel
{
public abstract class ChangeListener : INotifyPropertyChanged, IDisposable
{
#region *** Members ***
protected string _propertyName;
#endregion
 
 
#region *** Abstract Members ***
protected abstract void Unsubscribe();
#endregion
 
 
#region *** INotifyPropertyChanged Members and Invoker ***
public event PropertyChangedEventHandler PropertyChanged;
 
protected virtual void RaisePropertyChanged(string propertyName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
 
 
#region *** Disposable Pattern ***
 
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
 
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Unsubscribe();
}
}
 
~ChangeListener()
{
Dispose(false);
}
 
#endregion
 
 
#region *** Factory ***
public static ChangeListener Create(INotifyPropertyChanged value)
{
return Create(value, null);
}
 
public static ChangeListener Create(INotifyPropertyChanged value, string propertyName)
{
if (value is INotifyCollectionChanged)
{
return new CollectionChangeListener(value as INotifyCollectionChanged, propertyName);
}
else if (value is INotifyPropertyChanged)
{
return new ChildChangeListener(value as INotifyPropertyChanged, propertyName);
}
else
return null;
}
#endregion
}
}
ChildChangeListener.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
 
namespace ThomasJaworski.ComponentModel
{
public class ChildChangeListener : ChangeListener
{
#region *** Members ***
protected static readonly Type _inotifyType = typeof(INotifyPropertyChanged);
 
private readonly INotifyPropertyChanged _value;
private readonly Type _type;
private readonly Dictionary<string, ChangeListener> _childListeners = new Dictionary<string, ChangeListener>();
#endregion
 
 
#region *** Constructors ***
public ChildChangeListener(INotifyPropertyChanged instance)
{
if (instance == null)
throw new ArgumentNullException("instance");
 
_value = instance;
_type = _value.GetType();
 
Subscribe();
}
 
public ChildChangeListener(INotifyPropertyChanged instance, string propertyName)
: this(instance)
{
_propertyName = propertyName;
}
#endregion
 
 
#region *** Private Methods ***
private void Subscribe()
{
_value.PropertyChanged += new PropertyChangedEventHandler(value_PropertyChanged);
 
var query =
from property
in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
where _inotifyType.IsAssignableFrom(property.PropertyType)
select property;
 
foreach (var property in query)
{
// Declare property as known "Child", then register it
_childListeners.Add(property.Name, null);
ResetChildListener(property.Name);
}
}
 
 
/// <summary>
/// Resets known (must exist in children collection) child event handlers
/// </summary>
/// <param name="propertyName">Name of known child property</param>
private void ResetChildListener(string propertyName)
{
if (_childListeners.ContainsKey(propertyName))
{
// Unsubscribe if existing
if (_childListeners[propertyName] != null)
{
_childListeners[propertyName].PropertyChanged -= new PropertyChangedEventHandler(child_PropertyChanged);
 
// Should unsubscribe all events
_childListeners[propertyName].Dispose();
_childListeners[propertyName] = null;
}
 
var property = _type.GetProperty(propertyName);
if (property == null)
throw new InvalidOperationException(string.Format("Was unable to get '{0}' property information from Type '{1}'", propertyName, _type.Name));
 
object newValue = property.GetValue(_value, null);
 
// Only recreate if there is a new value
if (newValue != null)
{
if (newValue is INotifyCollectionChanged)
{
_childListeners[propertyName] =
new CollectionChangeListener(newValue as INotifyCollectionChanged, propertyName);
}
else if (newValue is INotifyPropertyChanged)
{
_childListeners[propertyName] =
new ChildChangeListener(newValue as INotifyPropertyChanged, propertyName);
}
 
if (_childListeners[propertyName] != null)
_childListeners[propertyName].PropertyChanged += new PropertyChangedEventHandler(child_PropertyChanged);
}
}
}
#endregion
 
 
#region *** Event Handler ***
void child_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(e.PropertyName);
}
 
void value_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// First, reset child on change, if required...
ResetChildListener(e.PropertyName);
 
// ...then, notify about it
RaisePropertyChanged(e.PropertyName);
}
 
protected override void RaisePropertyChanged(string propertyName)
{
// Special Formatting
base.RaisePropertyChanged(string.Format("{0}{1}{2}",
_propertyName, _propertyName != null ? "." : null, propertyName));
}
#endregion
 
 
#region *** Overrides ***
/// <summary>
/// Release all child handlers and self handler
/// </summary>
protected override void Unsubscribe()
{
_value.PropertyChanged -= new PropertyChangedEventHandler(value_PropertyChanged);
 
foreach (var binderKey in _childListeners.Keys)
{
if (_childListeners[binderKey] != null)
_childListeners[binderKey].Dispose();
}
 
_childListeners.Clear();
 
System.Diagnostics.Debug.WriteLine("ChildChangeListener '{0}' unsubscribed", _propertyName);
}
#endregion
}
}
CollectionChangeListener.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
 
namespace ThomasJaworski.ComponentModel
{
public class CollectionChangeListener : ChangeListener
{
#region *** Members ***
private readonly INotifyCollectionChanged _value;
private readonly Dictionary<INotifyPropertyChanged, ChangeListener> _collectionListeners = new Dictionary<INotifyPropertyChanged, ChangeListener>();
#endregion
 
 
#region *** Constructors ***
public CollectionChangeListener(INotifyCollectionChanged collection, string propertyName)
{
_value = collection;
_propertyName = propertyName;
 
Subscribe();
}
#endregion
 
 
#region *** Private Methods ***
private void Subscribe()
{
_value.CollectionChanged += new NotifyCollectionChangedEventHandler(value_CollectionChanged);
 
foreach (INotifyPropertyChanged item in (IEnumerable)_value)
{
ResetChildListener(item);
}
}
 
private void ResetChildListener(INotifyPropertyChanged item)
{
if (item == null)
throw new ArgumentNullException("item");
 
RemoveItem(item);
 
ChangeListener listener = null;
 
// Add new
if (item is INotifyCollectionChanged)
listener = new CollectionChangeListener(item as INotifyCollectionChanged, _propertyName);
else
listener = new ChildChangeListener(item as INotifyPropertyChanged);
 
listener.PropertyChanged += new PropertyChangedEventHandler(listener_PropertyChanged);
_collectionListeners.Add(item, listener);
}
 
private void RemoveItem(INotifyPropertyChanged item)
{
// Remove old
if (_collectionListeners.ContainsKey(item))
{
_collectionListeners[item].PropertyChanged -= new PropertyChangedEventHandler(listener_PropertyChanged);
 
_collectionListeners[item].Dispose();
_collectionListeners.Remove(item);
}
}
 
 
private void ClearCollection()
{
foreach (var key in _collectionListeners.Keys)
{
_collectionListeners[key].Dispose();
}
 
_collectionListeners.Clear();
}
#endregion
 
 
#region *** Event handlers ***
void value_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
ClearCollection();
}
else
{
// Don't care about e.Action, if there are old items, Remove them...
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged item in (IEnumerable)e.OldItems)
RemoveItem(item);
}
 
// ...add new items as well
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged item in (IEnumerable)e.NewItems)
ResetChildListener(item);
}
}
}
 
 
void listener_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// ...then, notify about it
RaisePropertyChanged(string.Format("{0}{1}{2}",
_propertyName, _propertyName != null ? "[]." : null, e.PropertyName));
}
#endregion
 
 
#region *** Overrides ***
/// <summary>
/// Releases all collection item handlers and self handler
/// </summary>
protected override void Unsubscribe()
{
ClearCollection();
 
_value.CollectionChanged -= new NotifyCollectionChangedEventHandler(value_CollectionChanged);
 
System.Diagnostics.Debug.WriteLine("CollectionChangeListener unsubscribed");
}
#endregion
}
}

How do I implement this? I saw the stackoverflow page. I tried to implement from there but the child properties are not being watched when their properties are changed. Do you have a demo bit of code?

@dantuck, you'd need something like this (partially taken from my original example @ StackOverflow).
Hope it works, I wrote that from scratch without compiler so you might need to correct little mistakes I made? ;-)

public class Person : INotifyPropertyChanged {

    private string _firstName;
    private int _age;
    private Person _bestFriend;

    public string FirstName {
        get { return _firstName; }
        set {
        // Short implementation for simplicity reasons
        _firstName = value;
        RaisePropertyChanged("FirstName");
        }
    }

    public int Age {
        get { return _age; }
        set {
        // Short implementation for simplicity reasons
        _age = value;
        RaisePropertyChanged("Age");
        }
    }

    public Person BestFriend {
        get { return _bestFriend; }
        set {
        _bestFriend = value;
        RaisePropertyChanged("BestFriend");
        }
    }

    #region *** INotifyPropertyChanged Members and Invoker ***
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged(string propertyName)
    {
    var temp = PropertyChanged;
    if (temp != null)
    temp(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

public static class Program {
    public static void Main() {

        Person p1 = new Person();
        var personChangeListener = ChangeListener.Create(p1);

        personChangeListener.PropertyChanged += new PropertyChangedEventHandler(value_PropertyChanged);

        // Start to Change P1... you should be notified about every change

        p1.Age = 30;
        p1.FirstName = "John";

        p1.BestFriend = new Person();

        p1.BestFriend.Age = 31;
        p1.BestFriend.FirstName = "Mike";

        // etc.
    }

    static void value_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("Changed Property: "+ e.PropertyName);
    }
}

Thanks. I got it working but have also started expanding to capture more info. On top of being notified I need the old and new value with the property for change tracking.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.