From d5f0110ca6a8e7a15308811ef3e314db3743a42e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 12 May 2020 12:57:58 +0200 Subject: [PATCH] Raise SelectionChanged when nested children change. --- src/Avalonia.Controls/SelectionNode.cs | 22 ++- .../SelectionModelTests.cs | 148 +++++++++++------- 2 files changed, 115 insertions(+), 55 deletions(-) diff --git a/src/Avalonia.Controls/SelectionNode.cs b/src/Avalonia.Controls/SelectionNode.cs index e25f88ff29..2fa7c5f697 100644 --- a/src/Avalonia.Controls/SelectionNode.cs +++ b/src/Avalonia.Controls/SelectionNode.cs @@ -8,6 +8,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using Avalonia.Controls.Utils; #nullable enable @@ -214,7 +215,21 @@ namespace Avalonia.Controls public void SetChildrenObservable(IObservable resolver) { - _childrenSubscription = resolver.Subscribe(x => Source = x); + _childrenSubscription = resolver.Subscribe(x => + { + if (Source != null) + { + using (_manager.Update()) + { + SelectionTreeHelper.Traverse( + this, + realizeChildren: false, + info => info.Node.Clear()); + } + } + + Source = x; + }); } public int SelectedCount { get; private set; } @@ -544,11 +559,14 @@ namespace Avalonia.Controls private void ClearChildNodes() { - foreach (var child in _childrenNodes) + for (int i = 0; i < _childrenNodes.Count; i++) { + var child = _childrenNodes[i]; + if (child != null && child != _manager.SharedLeafNode) { child.Dispose(); + _childrenNodes[i] = null; } } diff --git a/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs b/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs index 5d5669189e..337c0e6c5f 100644 --- a/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs @@ -1929,19 +1929,61 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Can_Replace_Children_Collection() + public void Can_Replace_Parent_Children_Collection() { var root = new Node("Root"); var target = new SelectionModel { Source = new[] { root } }; + var raised = 0; + target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children); target.Select(0, 9); - Assert.Equal("Child 9", ((Node)target.SelectedItem).Header); + var selected = (Node)target.SelectedItem; + Assert.Equal("Child 9", selected.Header); + + target.SelectionChanged += (s, e) => + { + Assert.Equal(new[] { Path(0, 9) }, e.DeselectedIndices); + Assert.Equal(new[] { selected }, e.DeselectedItems); + Assert.Empty(e.SelectedIndices); + Assert.Empty(e.SelectedItems); + ++raised; + }; + + root.ReplaceChildren(); + + Assert.Null(target.SelectedItem); + Assert.Equal(1, raised); + } + + [Fact] + public void Can_Replace_Grandparent_Children_Collection() + { + var root = new Node("Root"); + var target = new SelectionModel { Source = new[] { root } }; + var raised = 0; + + target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children); + + target.SelectAt(Path(0, 9, 1)); + + var selected = (Node)target.SelectedItem; + Assert.Equal("Child 1", selected.Header); + + target.SelectionChanged += (s, e) => + { + Assert.Equal(new[] { Path(0, 9, 1) }, e.DeselectedIndices); + Assert.Equal(new[] { selected }, e.DeselectedItems); + Assert.Empty(e.SelectedIndices); + Assert.Empty(e.SelectedItems); + ++raised; + }; root.ReplaceChildren(); Assert.Null(target.SelectedItem); + Assert.Equal(1, raised); } [Fact] @@ -1979,57 +2021,6 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, node.PropertyChangedSubscriptions); } - private class Node : INotifyPropertyChanged - { - private ObservableCollection _children; - private PropertyChangedEventHandler _propertyChanged; - - public Node(string header) - { - Header = header; - } - - public string Header { get; } - - public ObservableCollection Children - { - get => _children ??= CreateChildren(10); - private set - { - _children = value; - _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Children))); - } - } - - public event PropertyChangedEventHandler PropertyChanged - { - add - { - _propertyChanged += value; - ++PropertyChangedSubscriptions; - } - - remove - { - _propertyChanged -= value; - --PropertyChangedSubscriptions; - } - } - - public int PropertyChangedSubscriptions { get; private set; } - - public void ReplaceChildren() - { - Children = CreateChildren(5); - } - - private ObservableCollection CreateChildren(int count) - { - return new ObservableCollection( - Enumerable.Range(0, count).Select(x => new Node("Child " + x))); - } - } - private int GetSubscriberCount(AvaloniaList list) { return ((INotifyCollectionChangedDebug)list).GetCollectionChangedSubscribers()?.Length ?? 0; @@ -2396,6 +2387,57 @@ namespace Avalonia.Controls.UnitTests new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } + + private class Node : INotifyPropertyChanged + { + private ObservableCollection _children; + private PropertyChangedEventHandler _propertyChanged; + + public Node(string header) + { + Header = header; + } + + public string Header { get; } + + public ObservableCollection Children + { + get => _children ??= CreateChildren(10); + private set + { + _children = value; + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Children))); + } + } + + public event PropertyChangedEventHandler PropertyChanged + { + add + { + _propertyChanged += value; + ++PropertyChangedSubscriptions; + } + + remove + { + _propertyChanged -= value; + --PropertyChangedSubscriptions; + } + } + + public int PropertyChangedSubscriptions { get; private set; } + + public void ReplaceChildren() + { + Children = CreateChildren(5); + } + + private ObservableCollection CreateChildren(int count) + { + return new ObservableCollection( + Enumerable.Range(0, count).Select(x => new Node("Child " + x))); + } + } } class CustomSelectionModel : SelectionModel