Index: Core/Common/src/Core.Common.Base/RecursiveObserver.cs =================================================================== diff -u -r07b0e83704b56684d617cd6e8b8570417654912a -rf65694c90375dbbed8fea4db8e7d0873cc659669 --- Core/Common/src/Core.Common.Base/RecursiveObserver.cs (.../RecursiveObserver.cs) (revision 07b0e83704b56684d617cd6e8b8570417654912a) +++ Core/Common/src/Core.Common.Base/RecursiveObserver.cs (.../RecursiveObserver.cs) (revision f65694c90375dbbed8fea4db8e7d0873cc659669) @@ -25,95 +25,115 @@ namespace Core.Common.Base { /// - /// Class that implements in a way that a hierarchy of objects can be observed recursively. + /// Class that implements in a way that a hierarchy of objects can be observed. /// /// - /// The root being observed by instances of this class can be dynamically changed. + /// The root container () being observed by instances of this class can be dynamically changed. /// - /// The type of objects to observe recursively. - public class RecursiveObserver : IObserver, IDisposable where T : class, IObservable + /// The type of the item containers that specify the object hierarchy. + /// The type of items (in the containers) that should be observed. + public class RecursiveObserver : IObserver, IDisposable + where TContainer : class, IObservable + where TObservable : class, IObservable { - private T rootObservable; + private TContainer rootContainer; private readonly Action updateObserverAction; - private readonly Func> getChildObservables; - private readonly IList observedObjects = new List(); + private readonly Func> getChildren; + private readonly IList observedContainers = new List(); + private readonly IList observedChildren = new List(); + private readonly Observer containerObserver; /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// - /// The action to perform on notifications coming from one of the items of the hierarchy of observed objects. - /// The method used for recursively obtaining the objects to observe. - public RecursiveObserver(Action updateObserverAction, Func> getChildObservables) + /// The action to perform on notifications coming from one of the items of the hierarchy. + /// The method used for recursively obtaining the children of objects in the hierarchy. + public RecursiveObserver(Action updateObserverAction, Func> getChildren) { this.updateObserverAction = updateObserverAction; - this.getChildObservables = getChildObservables; + this.getChildren = getChildren; + + // Ensure subscriptions are updated (detach/attach) on changes in the hierarchy + containerObserver = new Observer(UpdateObservedObjects); } /// - /// Gets or sets the root object to observe. + /// Gets or sets the root container. /// - public T Observable + public TContainer Observable { get { - return rootObservable; + return rootContainer; } set { - rootObservable = value; + rootContainer = value; UpdateObservedObjects(); } } + public void Dispose() + { + Observable = null; + } + public void UpdateObserver() { updateObserverAction(); - - // Update the list of observed objects as observables might have been added/removed - UpdateObservedObjects(); } private void UpdateObservedObjects() { - // Detach from the currently observed objects - foreach (var observedObject in observedObjects) + // Detach from the currently observed containers + foreach (var observedObject in observedContainers) { + observedObject.Detach(containerObserver); + } + + // Detach from the currently observed children + foreach (var observedObject in observedChildren) + { observedObject.Detach(this); } - // Clear the list of observed objects - observedObjects.Clear(); + // Clear the lists of observed objects + observedContainers.Clear(); + observedChildren.Clear(); // If relevant, start observing objects again - if (rootObservable != null) + if (rootContainer != null) { - foreach (var objectToObserve in GetObservablesRecursive(rootObservable)) - { - objectToObserve.Attach(this); - observedObjects.Add(objectToObserve); - } + ObserveObjectsRecursively(rootContainer); } } - private IEnumerable GetObservablesRecursive(T observable) + private void ObserveObjectsRecursively(TContainer container) { - var observables = new List - { - observable - }; + container.Attach(containerObserver); + observedContainers.Add(container); - foreach (var childObservable in getChildObservables(observable)) + var observable = container as TObservable; + if (observable != null) { - observables.AddRange(GetObservablesRecursive(childObservable)); + observable.Attach(this); + observedChildren.Add(observable); } - return observables; + foreach (var child in getChildren(container)) + { + if (child is TContainer) + { + ObserveObjectsRecursively((TContainer) child); + } + else if (child is TObservable) + { + observable = (TObservable) child; + observable.Attach(this); + observedChildren.Add(observable); + } + } } - - public void Dispose() - { - Observable = null; - } } } Index: Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs =================================================================== diff -u -rb04b130df2b11fd54962e4a0b9be92265e580831 -rf65694c90375dbbed8fea4db8e7d0873cc659669 --- Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs (.../RecursiveObserverTest.cs) (revision b04b130df2b11fd54962e4a0b9be92265e580831) +++ Core/Common/test/Core.Common.Base.Test/RecursiveObserverTest.cs (.../RecursiveObserverTest.cs) (revision f65694c90375dbbed8fea4db8e7d0873cc659669) @@ -34,7 +34,7 @@ var counter = 0; // Call - var recursiveObserver = new RecursiveObserver(() => { counter++; }, GetChildObservables); + var recursiveObserver = new RecursiveObserver(() => { counter++; }, GetChildren); // Assert Assert.IsInstanceOf(recursiveObserver); @@ -49,159 +49,260 @@ [TestCase(1)] [TestCase(2)] [TestCase(100)] - public void RecursiveObserver_WithObservableHierarchy_NotifyObserversAtSpecifiedLevelResultsInPerformingUpdateObserversAction(int nestingLevel) + public void RecursiveObserverObservingContainers_NotifyObserversAtSpecifiedLevel_UpdateObserversActionShouldBePerformed(int nestingLevel) { - // Setup + // Given var counter = 0; - var rootObservable = new TestObservable(); + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); - var currentNestedObservable = rootObservable; + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); - for (var i = 0; i < nestingLevel; i++) + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - var newObservable = new TestObservable(); + Observable = rootContainer + }; - currentNestedObservable.ChildTestObservables.Add(newObservable); + // When + currentNestedContainer.NotifyObservers(); - currentNestedObservable = newObservable; - } + // Then + Assert.AreEqual(1, counter); - var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables) + currentTestObservable.NotifyObservers(); + Assert.AreEqual(1, counter); // Nothing should have happened + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(100)] + public void RecursiveObserverObservingItemsInContainers_NotifyObserversAtSpecifiedLevel_UpdateObserversActionShouldBePerformed(int nestingLevel) + { + // Given + var counter = 0; + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); + + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); + + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - Observable = rootObservable + Observable = rootContainer }; - // Call - currentNestedObservable.NotifyObservers(); + // When + currentTestObservable.NotifyObservers(); - // Assert + // Then Assert.AreEqual(1, counter); + + currentNestedContainer.NotifyObservers(); + Assert.AreEqual(1, counter); // Nothing should have happened } [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(100)] - public void RecursiveObserver_WithObservableHierarchySetAndThenUnset_NotifyObserversNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel) + public void RecursiveObserverObservingContainers_RootContainerSetAndThenUnset_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) { - // Setup + // Given var counter = 0; - var rootObservable = new TestObservable(); + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); - var currentNestedObservable = rootObservable; + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); - for (var i = 0; i < nestingLevel; i++) + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - var newObservable = new TestObservable(); + Observable = rootContainer + }; - currentNestedObservable.ChildTestObservables.Add(newObservable); + // When + recursiveObserver.Observable = null; + currentNestedContainer.NotifyObservers(); - currentNestedObservable = newObservable; - } + // Then + Assert.AreEqual(0, counter); + } - var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables) + [TestCase(1)] + [TestCase(2)] + [TestCase(100)] + public void RecursiveObserverObservingItemsInContainers_RootContainerSetAndThenUnset_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) + { + // Given + var counter = 0; + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); + + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); + + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - Observable = rootObservable + Observable = rootContainer }; + // When recursiveObserver.Observable = null; + currentTestObservable.NotifyObservers(); - // Call - currentNestedObservable.NotifyObservers(); - - // Assert + // Then Assert.AreEqual(0, counter); } [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(100)] - public void RecursiveObserver_WithObservableHierarchySetAndThenDisposed_NotifyObserversNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel) + public void RecursiveObserverObservingContainers_RecursiveObserverDispose_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) { - // Setup + // Given var counter = 0; - var rootObservable = new TestObservable(); + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); - var currentNestedObservable = rootObservable; + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); - for (var i = 0; i < nestingLevel; i++) + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - var newObservable = new TestObservable(); + Observable = rootContainer + }; - currentNestedObservable.ChildTestObservables.Add(newObservable); + // When + recursiveObserver.Dispose(); + currentNestedContainer.NotifyObservers(); - currentNestedObservable = newObservable; - } + // Then + Assert.AreEqual(0, counter); + Assert.IsNull(recursiveObserver.Observable); + } - var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables) + [TestCase(1)] + [TestCase(2)] + [TestCase(100)] + public void RecursiveObserverObservingItemsInContainers_RecursiveObserverDispose_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) + { + // Given + var counter = 0; + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); + + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); + + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - Observable = rootObservable + Observable = rootContainer }; + // When recursiveObserver.Dispose(); + currentTestObservable.NotifyObservers(); - // Call - currentNestedObservable.NotifyObservers(); - - // Assert + // Then Assert.AreEqual(0, counter); + Assert.IsNull(recursiveObserver.Observable); } [TestCase(1)] [TestCase(2)] [TestCase(100)] - public void RecursiveObserver_WithObservableHierarchySetAndThenObservedItemRemoved_NotifyObserversForRemovedItemNoLongerResultsInPerformingUpdateObserversAction(int nestingLevel) + public void RecursiveObserverObservingContainers_ContainerItemsRemoved_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) { - // Setup + // Given var counter = 0; - var rootObservable = new TestObservable(); + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); - var previousNestedObservable = new TestObservable(); - var currentNestedObservable = rootObservable; + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); - for (var i = 0; i < nestingLevel; i++) + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - var newObservable = new TestObservable(); + Observable = rootContainer + }; - currentNestedObservable.ChildTestObservables.Add(newObservable); + // When + rootContainer.Children.Clear(); + rootContainer.NotifyObservers(); // Collection changes should always be notified - previousNestedObservable = currentNestedObservable; - currentNestedObservable = newObservable; - } + // Precondition (counter equals 1 due to previous notification) + Assert.AreEqual(1, counter); - var recursiveObserver = new RecursiveObserver(() => counter++, GetChildObservables) + currentNestedContainer.NotifyObservers(); + + // Then + Assert.AreEqual(1, counter); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(100)] + public void RecursiveObserverObservingItemsInContainers_ContainerItemsRemoved_UpdateObserversActionShouldNoLongerBePerformed(int nestingLevel) + { + // Given + var counter = 0; + var rootContainer = new TestContainer(); + var currentNestedContainer = rootContainer; + var currentTestObservable = new TestObservable(); + + InitializeHierarchy(nestingLevel, ref currentNestedContainer, ref currentTestObservable); + + var recursiveObserver = new RecursiveObserver(() => counter++, GetChildren) { - Observable = rootObservable + Observable = rootContainer }; - previousNestedObservable.ChildTestObservables.Clear(); - previousNestedObservable.NotifyObservers(); - counter = 0; + // When + rootContainer.Children.Clear(); + rootContainer.NotifyObservers(); // Collection changes should always be notified - // Call - currentNestedObservable.NotifyObservers(); + currentTestObservable.NotifyObservers(); - // Assert + // Then Assert.AreEqual(0, counter); } - private class TestObservable : Observable + private static void InitializeHierarchy(int nestingLevel, ref TestContainer currentNestedContainer, ref TestObservable currentTestObservable) { - private readonly IList childTestObservables = new List(); + for (var i = 0; i < nestingLevel; i++) + { + var newNestedContainer = new TestContainer(); + var newTestObservable = new TestObservable(); - public IList ChildTestObservables + currentNestedContainer.Children.Add(new object()); + currentNestedContainer.Children.Add(newTestObservable); + currentNestedContainer.Children.Add(newNestedContainer); + + currentTestObservable = newTestObservable; + currentNestedContainer = newNestedContainer; + } + } + + private class TestContainer : Observable + { + private readonly IList children = new List(); + + public IList Children { get { - return childTestObservables; + return children; } } } - private IEnumerable GetChildObservables(TestObservable testObservable) + private class TestObservable : Observable { } + + private IEnumerable GetChildren(TestContainer container) { - return testObservable.ChildTestObservables; + return container.Children; } } } Index: Ringtoets/Piping/src/Ringtoets.Piping.Forms/Views/PipingCalculationsView.cs =================================================================== diff -u -r088f1f6f74733f2b37f6b79b434026bdbc34c941 -rf65694c90375dbbed8fea4db8e7d0873cc659669 --- Ringtoets/Piping/src/Ringtoets.Piping.Forms/Views/PipingCalculationsView.cs (.../PipingCalculationsView.cs) (revision 088f1f6f74733f2b37f6b79b434026bdbc34c941) +++ Ringtoets/Piping/src/Ringtoets.Piping.Forms/Views/PipingCalculationsView.cs (.../PipingCalculationsView.cs) (revision f65694c90375dbbed8fea4db8e7d0873cc659669) @@ -41,7 +41,7 @@ { private readonly Observer pipingSoilProfilesObserver; private readonly Observer assessmentSectionObserver; - private readonly RecursiveObserver pipingCalculationGroupObserver; + private readonly RecursiveObserver pipingCalculationGroupObserver; private AssessmentSectionBase assessmentSection; private PipingFailureMechanism pipingFailureMechanism; private PipingCalculationGroup pipingCalculationGroup; @@ -58,7 +58,7 @@ pipingSoilProfilesObserver = new Observer(UpdateSoilProfileColumn); assessmentSectionObserver = new Observer(UpdateHydraulicBoundaryLocationsColumn); - pipingCalculationGroupObserver = new RecursiveObserver(UpdateDataGridViewDataSource, pg => pg.Children.OfType()); + pipingCalculationGroupObserver = new RecursiveObserver(UpdateDataGridViewDataSource, pg => pg.Children); } ///