/// <summary> /// Binds the specified <see cref="PropertyChangedEventHandler"/> on _all_ observables (including via <see cref="INotifyCollectionChanged"/>) /// reachable via reflection that have a property with the specified name and type, and invokes the handler on those event that match the predicate. /// </summary> public static IDisposable BindRecursively <TPropertyType>(this INotifyPropertyChanged root, string propertyName, PropertyMutatedEventHandler <TPropertyType> handler) { return(root.BindRecursively(predicate, handlerWrapper)); bool predicate(object obj, IPropertyMutatedEventArgs e) { if (e.PropertyName == propertyName) { var propertyType = GetPropertyType(obj.GetType(), propertyName); var result = typeof(TPropertyType).IsAssignableFrom(propertyType); return(result); } return(false); } void handlerWrapper(object sender, IPropertyMutatedEventArgs e) { handler(sender, (PropertyMutatedEventArgs <TPropertyType>)e); } }
/// <summary> /// Binds the specified <see cref="PropertyChangedEventHandler"/> on _all_ observables (including via <see cref="INotifyCollectionChanged"/>) /// reachable via reflection, and invokes the handler on those event that match the predicate. /// </summary> /// <param name="predicate"> A predicate for invoking the handler. (not a predicate for hooking the handler). </param> public static IDisposable BindRecursively(this INotifyPropertyChanged root, Func <object, IPropertyMutatedEventArgs, bool> predicate, PropertyMutatedEventHandler handler) { Contract.Requires(root != null); Contract.Requires(predicate != null); Contract.Requires(handler != null); var allHookedObservables = new Dictionary <object, int>(ReferenceEqualityComparer.Instance); // integer is reference counts by this dictionary addReference(root); #if DEBUG return(new BindRecursivelyDisposable(allHookedObservables, () => #else return new Disposable(() => #endif { foreach (var obj in allHookedObservables) { unhook(obj); } allHookedObservables.Clear(); })); void hook(object tree) { foreach (object obj in getEntireObjectTree(tree)) { var deltaReferenceCount = 0; if (obj is INotifyPropertyChanged container) { container.PropertyChanged += handle; deltaReferenceCount++; } if (obj is INotifyCollectionChanged collection) { collection.CollectionChanged += handleCollectionChanged; deltaReferenceCount++; } if (deltaReferenceCount != 0) { allHookedObservables[obj] = allHookedObservables.GetOrAdd(obj, 0) + deltaReferenceCount; } } } void unhook(object tree) { Contract.Assume(allHookedObservables.ContainsKey(tree)); foreach (object obj in getEntireObjectTree(tree)) { var deltaReferenceCount = 0; if (obj is INotifyPropertyChanged container) { container.PropertyChanged -= handle; deltaReferenceCount--; } if (obj is INotifyCollectionChanged collection) { collection.CollectionChanged -= handleCollectionChanged; deltaReferenceCount--; } if (deltaReferenceCount != 0) { allHookedObservables[obj] += deltaReferenceCount; } } } void addReference(object newReferredObject) { if (newReferredObject != null && allHookedObservables.ContainsKey(newReferredObject)) { allHookedObservables[newReferredObject]++; } else { hook(newReferredObject); } } void removeReference(object oldReferredObject) { if (oldReferredObject != null && allHookedObservables.ContainsKey(oldReferredObject)) { if (--allHookedObservables[oldReferredObject] == 0) { unhook(oldReferredObject); } } } void updateReferences(object oldReferredObject, object newReferredObject) { if (ReferenceEquals(oldReferredObject, newReferredObject)) { return; } addReference(newReferredObject); removeReference(oldReferredObject); } void handle(object sender, PropertyChangedEventArgs originalE) { if (originalE is IPropertyMutatedEventArgs e) { updateReferences(e.OldValue, e.NewValue); if (predicate(sender, e)) { handler.Invoke(sender, e); } } } void handleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var newObj in e.NewItems) { addReference(newObj); } break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var newObj in e.OldItems) { removeReference(newObj); } break; case NotifyCollectionChangedAction.Replace: foreach (var newObj in e.NewItems) { addReference(newObj); } foreach (var newObj in e.OldItems) { removeReference(newObj); } break; case NotifyCollectionChangedAction.Move: break; default: throw new InvalidOperationException(); } } }
/// <summary> Invokes the specified handler when a property on a property changes, or when the source changes, /// and, if the current value differs from the specified default value, at the end of this call. </summary> /// <param name="propertyName"> The name of the property on the source. </param> /// <param name="propertyOnPropertyName"> The name of the property on the property on the source. </param> /// <param name="handler"> The handler to be invoked at the moments listed above. </param> /// <param name="defaultValue"> The value that is the default of the property on the property (used as old value when the property containing that property is null). </param> public static void Bind <T>(this INotifyPropertyChanged source, string propertyName, string propertyOnPropertyName, PropertyMutatedEventHandler <T> handler, T defaultValue) { Bind(source, propertyName, propertyOnPropertyName, (sender, e) => handler(sender, (PropertyMutatedEventArgs <T>)e), defaultValue, typeof(T)); }
/// <summary> Invokes the specified handler when a property on a property changes, or when the source changes, /// and, if the current value differs from the default value, at the end of this call. </summary> /// <param name="propertyName"> The name of the property on the source. </param> /// <param name="propertyOnPropertyName"> The name of the property on the property on the source. </param> /// <param name="handler"> The handler to be invoked at the moments listed above. </param> public static void Bind <T>(this INotifyPropertyChanged source, string propertyName, string propertyOnPropertyName, PropertyMutatedEventHandler <T> handler) { // this overload makes use of the feature of the other overload to correct the default value if it is null to the default Bind(source, propertyName, propertyOnPropertyName, (sender, e) => handler(sender, (PropertyMutatedEventArgs <T>)e), null); }