瀏覽代碼

Merge pull request #11404 from AvaloniaUI/fixes/treeview-clear-selection

Fix clearing TreeView selection when item removed.
Dan Walmsley 2 年之前
父節點
當前提交
d2d37a09e6

+ 17 - 0
src/Avalonia.Controls/TreeView.cs

@@ -293,6 +293,23 @@ namespace Avalonia.Controls
             return TreeItemFromContainer(this, container);
         }
 
+        private protected override void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        {
+            base.OnItemsViewCollectionChanged(sender, e);
+
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Remove:
+                case NotifyCollectionChangedAction.Replace:
+                    foreach (var i in e.OldItems!)
+                        SelectedItems.Remove(i);
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    SelectedItems.Clear();
+                    break;
+            }
+        }
+
         /// <summary>
         /// Subscribes to the <see cref="SelectedItems"/> CollectionChanged event, if any.
         /// </summary>

+ 26 - 0
src/Avalonia.Controls/TreeViewItem.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Linq;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Selection;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Input;
@@ -289,6 +291,30 @@ namespace Avalonia.Controls
             }
         }
 
+        private protected override void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        {
+            base.OnItemsViewCollectionChanged(sender, e);
+
+            if (_treeView is null)
+                return;
+
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Remove:
+                case NotifyCollectionChangedAction.Replace:
+                    foreach (var i in e.OldItems!)
+                        _treeView.SelectedItems.Remove(i);
+                    break;
+                case NotifyCollectionChangedAction.Reset:
+                    foreach (var i in GetRealizedContainers())
+                    {
+                        if (i is TreeViewItem tvi && tvi.IsSelected)
+                            _treeView.SelectedItems.Remove(i.DataContext);
+                    }
+                    break;
+            }
+        }
+
         private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class
         {
             var result = 0;

+ 80 - 0
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -724,6 +724,86 @@ namespace Avalonia.Controls.UnitTests
             Assert.True(called);
         }
 
+        [Fact]
+        public void Removing_Selected_Root_Item_Should_Clear_Selection()
+        {
+            using var app = Start();
+            var data = CreateTestTreeData();
+            var target = CreateTarget(data: data);
+            var item = data[0];
+
+            target.SelectedItem = item;
+
+            data.RemoveAt(0);
+
+            Assert.Null(target.SelectedItem);
+            Assert.Empty(target.SelectedItems);
+        }
+
+        [Fact]
+        public void Resetting_Root_Items_Should_Clear_Selection()
+        {
+            using var app = Start();
+            var data = CreateTestTreeData();
+            var target = CreateTarget(data: data);
+            var item = data[0];
+
+            target.SelectedItem = item;
+
+            data.Clear();
+
+            Assert.Null(target.SelectedItem);
+            Assert.Empty(target.SelectedItems);
+        }
+
+        [Fact]
+        public void Removing_Selected_Child_Item_Should_Clear_Selection()
+        {
+            using var app = Start();
+            var data = CreateTestTreeData();
+            var target = CreateTarget(data: data);
+            var item = data[0].Children[1];
+
+            target.SelectedItem = item;
+
+            data[0].Children.RemoveAt(1);
+
+            Assert.Null(target.SelectedItem);
+            Assert.Empty(target.SelectedItems);
+        }
+
+        [Fact]
+        public void Replacing_Selected_Child_Item_Should_Clear_Selection()
+        {
+            using var app = Start();
+            var data = CreateTestTreeData();
+            var target = CreateTarget(data: data);
+            var item = data[0].Children[1];
+
+            target.SelectedItem = item;
+
+            data[0].Children[1] = new Node();
+
+            Assert.Null(target.SelectedItem);
+            Assert.Empty(target.SelectedItems);
+        }
+
+        [Fact]
+        public void Clearing_Child_Items_Should_Clear_Selection()
+        {
+            using var app = Start();
+            var data = CreateTestTreeData();
+            var target = CreateTarget(data: data);
+            var item = data[0].Children[1];
+
+            target.SelectedItem = item;
+
+            data[0].Children.Clear();
+
+            Assert.Null(target.SelectedItem);
+            Assert.Empty(target.SelectedItems);
+        }
+
         [Fact]
         public void SelectedItem_Should_Be_Valid_When_SelectedItemChanged_Event_Raised()
         {