public void ReductionBindTriggersHandler() { // Arrange const int expected = 2; var collection = new ProperObservableCollection <Giraffe>(); collection.BindCollective <Giraffe, int>(nameof(Giraffe.Property), OnCollectivePropertyChanged, Math.Min); int result = -1; void OnCollectivePropertyChanged(object sender, PropertyChangedEventArgs e) { result = ((PropertyMutatedEventArgs <int>)e).NewValue; } // Act collection.Add(new Giraffe() { Property = expected + 1 }); collection.Add(new Giraffe() { Property = expected }); Assert.AreEqual(expected, result); }
//TODO: make the above readonly collections /// <summary> /// Syncs the contents of two observable collections by two selectors. /// </summary> public static ProperObservableCollection <TResult> SyncSelect <T, TResult>(this ProperObservableCollection <T> collection, Func <T, TResult> selector, Func <TResult, T> reverseSelector) { var result = new ProperObservableCollection <TResult>(); Sync <T, TResult>(collection, result, selector, reverseSelector); return(result); }
public void PropertyOnCollectionChangedIsPropagated() { bool eventHandlerInvoked = false; var collection = new ProperObservableCollection <Giraffe>(); collection.Bind(nameof(Giraffe.Property), (sender, e) => { eventHandlerInvoked = true; }); collection.Add(new Giraffe()); Assert.IsFalse(eventHandlerInvoked); collection[0].Property = 0; Assert.IsFalse(eventHandlerInvoked); collection[0].Property2 = 1; Assert.IsFalse(eventHandlerInvoked); collection[0].Property = 1; Assert.IsTrue(eventHandlerInvoked); }
/// <summary> /// Creates an observable collection whose content reflects the content of the specified collection, filtered by the specified predicate and mapped by the specified selector. /// This collection is kept up-to-date with respect to changes in the original colletion. /// </summary> public static MyReadOnlyObservableCollection <TResult> WhereSelectLive <T, TResult>(this IReadOnlyObservableCollection <T> collection, Func <T, bool> predicate, Func <T, TResult> selector) { Contract.Requires(collection != null); Contract.Requires(selector != null); var result = new ProperObservableCollection <TResult>(); collection.CollectionChanged += collectionChanged; collectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection)); return(new MyReadOnlyObservableCollection <TResult>(result)); void collectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Contract.Requires(sender == collection); switch (e.Action) { case NotifyCollectionChangedAction.Add: result.InsertRange(e.NewStartingIndex, e.NewItems.Cast <T>().Where(predicate).Select(selector)); break; case NotifyCollectionChangedAction.Remove: result.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break; case NotifyCollectionChangedAction.Replace: result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Where(predicate).Select(selector)); break; case NotifyCollectionChangedAction.Move: result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex); break; case NotifyCollectionChangedAction.Reset: result.Clear(); break; default: throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e)); } } }
/// <summary> /// Syncs the contents of two observable collections by two selectors. /// </summary> public static void Sync <T, TResult>(this ProperObservableCollection <T> collection, ProperObservableCollection <TResult> secondCollection, Func <T, TResult> selector, Func <TResult, T> reverseSelector) { Contract.Requires(collection != null); Contract.Requires(selector != null); Contract.Requires(reverseSelector != null); bool preventBackSync = false; collection.CollectionChanged += collectionChanged; if (collection.Count != 0) { collectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection)); } secondCollection.CollectionChanged += reverseCollectionChanged; //returns whether the sync operation should be performed bool toggleSyncFlag() { preventBackSync = !preventBackSync; return(preventBackSync); } void collectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (toggleSyncFlag()) { collectionChanged <T, TResult>(e, secondCollection, selector); } } void reverseCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (toggleSyncFlag()) { collectionChanged <TResult, T>(e, collection, reverseSelector); } } }
public void PropertyOnCollectionChangedIsNotPropagatedIfRemoved() { //Arrange bool eventHandlerInvoked = false; var collection = new ProperObservableCollection <Giraffe>(); collection.Bind(nameof(Giraffe.Property), (sender, e) => { eventHandlerInvoked = true; }); var giraffe = new Giraffe(); collection.Add(giraffe); //Act collection.Remove(giraffe); giraffe.Property = 1; //Assert Assert.IsFalse(eventHandlerInvoked); }
private static void collectionChanged <T, TResult>(NotifyCollectionChangedEventArgs e, ProperObservableCollection <TResult> result, Func <T, TResult> selector) { Contract.Requires(e != null); Contract.Requires(result != null); Contract.Requires(selector != null); switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewStartingIndex == -1) { result.AddRange(e.NewItems.Cast <T>().Select(selector)); } else { result.InsertRange(e.NewStartingIndex, e.NewItems.Cast <T>().Select(selector)); } break; case NotifyCollectionChangedAction.Remove: result.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break; case NotifyCollectionChangedAction.Replace: result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Select(selector)); break; case NotifyCollectionChangedAction.Move: result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex); break; case NotifyCollectionChangedAction.Reset: result.Clear(); break; default: throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e)); } }
/// <summary> /// Creates an observable collection whose content reflects the content of the specified collection, filtered by the specified predicate and mapped by the specified many selector. /// This collection is kept up-to-date with respect to changes in the original colletion. /// </summary> public static MyReadOnlyObservableCollection <TResult> WhereSelectManyLive <T, TResult>(this ObservableCollection <T> collection, Func <T, bool> predicate, Func <T, IEnumerable <TResult> > selector) { Contract.Requires(collection != null); Contract.Requires(selector != null); List <int> elementCountsPerSourceElement = new List <int>(); int cumulativeElements(int sourceElementIndex) { return(elementCountsPerSourceElement.Take(sourceElementIndex).Sum()); } var result = new ProperObservableCollection <TResult>(); collection.CollectionChanged += collectionChanged; collectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection)); return(new MyReadOnlyObservableCollection <TResult>(result)); void collectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Contract.Requires(sender == collection); switch (e.Action) { case NotifyCollectionChangedAction.Add: { int sourceItemIndex = e.NewStartingIndex; int resultIndex = cumulativeElements(e.NewStartingIndex); foreach (var newSourceItem in e.NewItems.Cast <T>().Where(predicate)) { int originalResultCount = result.Count; result.InsertRange(resultIndex, selector(newSourceItem)); int selectedElementCount = result.Count - originalResultCount; resultIndex += selectedElementCount; elementCountsPerSourceElement.Insert(sourceItemIndex++, selectedElementCount); } break; } case NotifyCollectionChangedAction.Remove: { int resultIndex = cumulativeElements(e.OldStartingIndex); int resultToRemoveCount = elementCountsPerSourceElement.Skip(e.OldStartingIndex).Take(e.OldItems.Count).Sum(); result.RemoveRange(resultIndex, resultToRemoveCount); elementCountsPerSourceElement.RemoveRange(e.OldStartingIndex, e.OldItems.Count); break; } case NotifyCollectionChangedAction.Replace: result.Replace(e.OldStartingIndex, e.OldItems.Count, e.NewItems.Cast <T>().Where(predicate).SelectMany(selector)); break; case NotifyCollectionChangedAction.Move: result.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex); break; case NotifyCollectionChangedAction.Reset: result.Clear(); break; default: throw new ArgumentException("No defined NotifyCollectionChangedEventArgs.Action specified", nameof(e)); } } }