/// <summary> Invokes the specified handler when a collection property on a property changes, when the source changes, /// or when the collection changes. </summary> /// <param name="propertyName"> The name of the property on the source. </param> /// <param name="collectionPropertyOnPropertyName"> The name of the collection property on the property on the source, i.e. <code>source.property.collectionProperty</code></param> /// <param name="handler"> The handler to be invoked at the moments listed above. </param> public static void BindCollection(this INotifyPropertyChanged source, string propertyName, string collectionPropertyOnPropertyName, NotifyCollectionChangedEventHandler handler) { Contract.Requires(GetPropertyType(source.GetType(), propertyName, collectionPropertyOnPropertyName).Implements(typeof(INotifyCollectionChanged)), $"The collection property '{collectionPropertyOnPropertyName}' on the source must implement '{nameof(INotifyCollectionChanged)}'"); source.Bind(propertyName, collectionPropertyOnPropertyName, RebindCollectionChangedHandler); void RebindCollectionChangedHandler(object sender, PropertyChangedEventArgs eventArg) { IPropertyMutatedEventArgs e = (IPropertyMutatedEventArgs)eventArg; Contract.Requires(sender != null); Contract.Requires(e != null); Contract.Requires(e.OldValue == null || e.OldValue.GetType().Implements(typeof(INotifyCollectionChanged)), "The collection instance must implement 'INotifyCollectionChanged'"); Contract.Requires(e.NewValue == null || e.NewValue.GetType().Implements(typeof(INotifyCollectionChanged)), "The collection instance must implement 'INotifyCollectionChanged'"); Contract.Requires(e.OldValue == null || e.OldValue.GetType().Implements(typeof(System.Collections.IList)), "The collection instance must implement 'IList'"); Contract.Requires(e.NewValue == null || e.NewValue.GetType().Implements(typeof(System.Collections.IList)), "The collection instance must implement 'IList'"); if (e.OldValue is INotifyCollectionChanged oldCollection) { oldCollection.CollectionChanged -= handler; } if (e.NewValue is INotifyCollectionChanged newCollection) { newCollection.CollectionChanged += handler; handler(sender, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newCollection, e.OldValue as System.Collections.IList)); } } }
private static Action ForwardPropertyChanged(this INotifyPropertyChanged source, ImmutableQueue <JsonPathNode> dependencyNodes, INotifyPropertyChanged target, string targetPropertyName) { WeakReference <INotifyPropertyChanged> wr = new WeakReference <INotifyPropertyChanged>(target); Action unsubscribe = null; dependencyNodes = dependencyNodes.Dequeue(out var node); switch (node) { case JsonPathPropertySelectorNode propertySelectorNode: string sourcePropertyName = propertySelectorNode.PropertyName; unsubscribe = source.ForwardPropertyChanged(sourcePropertyName, target, targetPropertyName); if (dependencyNodes.IsEmpty) { return(unsubscribe); } Action unsubscribeNested = null; if (propertySelectorNode.GetValue(source) is INotifyPropertyChanged nestedSource) { unsubscribeNested = nestedSource.ForwardPropertyChanged(dependencyNodes, target, targetPropertyName); unsubscribe += unsubscribeNested; } unsubscribe += source.Bind <INotifyPropertyChanged, object>(sourcePropertyName, (value) => { unsubscribe -= unsubscribeNested; unsubscribeNested?.Invoke(); if (!wr.TryGetTarget(out var t)) { return; } if (value is INotifyPropertyChanged nestedSrc) { unsubscribeNested = nestedSrc.ForwardPropertyChanged(dependencyNodes, t, targetPropertyName); unsubscribe += unsubscribeNested; } });