Explorar o código

Merge pull request #5788 from AvaloniaUI/fixes/selectionmodel-reset

Correctly sync SelectedItems on collection reset.
Steven Kirk %!s(int64=4) %!d(string=hai) anos
pai
achega
7b5cfc04e0

+ 43 - 7
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@@ -13,8 +13,9 @@ namespace Avalonia.Controls.Selection
     internal class InternalSelectionModel : SelectionModel<object?>
     {
         private IList? _writableSelectedItems;
-        private bool _ignoreModelChanges;
+        private int _ignoreModelChanges;
         private bool _ignoreSelectedItemsChanges;
+        private bool _isResetting;
 
         public InternalSelectionModel()
         {
@@ -130,17 +131,31 @@ namespace Avalonia.Controls.Selection
 
             try
             {
-                _ignoreModelChanges = true;
+                ++_ignoreModelChanges;
 
                 using (BatchUpdate())
                 {
                     Clear();
-                    Add(_writableSelectedItems);
+
+                    for (var i = 0; i < _writableSelectedItems.Count; ++i)
+                    {
+                        var index = IndexOf(Source, _writableSelectedItems[i]);
+
+                        if (index != -1)
+                        {
+                            Select(index);
+                        }
+                        else
+                        {
+                            _writableSelectedItems.RemoveAt(i);
+                            --i;
+                        }
+                    }
                 }
             }
             finally
             {
-                _ignoreModelChanges = false;
+                --_ignoreModelChanges;
             }
         }
 
@@ -162,7 +177,7 @@ namespace Avalonia.Controls.Selection
 
         private void OnSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
         {
-            if (_ignoreModelChanges)
+            if (_ignoreModelChanges > 0)
             {
                 return;
             }
@@ -191,6 +206,27 @@ namespace Avalonia.Controls.Selection
             }
         }
 
+        private protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Reset)
+            {
+                ++_ignoreModelChanges;
+                _isResetting = true;
+            }
+
+            base.OnSourceCollectionChanged(e);
+        }
+
+        protected override void OnSourceCollectionChangeFinished()
+        {
+            base.OnSourceCollectionChangeFinished();
+
+            if (_isResetting)
+            {
+                --_ignoreModelChanges;
+            }
+        }
+
         private void OnSourceReset(object sender, EventArgs e) => SyncFromSelectedItems();
 
         private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@@ -222,7 +258,7 @@ namespace Avalonia.Controls.Selection
             {
                 using var operation = BatchUpdate();
 
-                _ignoreModelChanges = true;
+                ++_ignoreModelChanges;
 
                 switch (e.Action)
                 {
@@ -244,7 +280,7 @@ namespace Avalonia.Controls.Selection
             }
             finally
             {
-                _ignoreModelChanges = false;
+                --_ignoreModelChanges;
             }
         }
 

+ 7 - 0
tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs

@@ -97,6 +97,7 @@ namespace Avalonia.Controls.UnitTests.Selection
             target.WritableSelectedItems.Clear();
 
             Assert.Empty(target.SelectedIndexes);
+            Assert.Empty(target.WritableSelectedItems);
         }
 
         [Fact]
@@ -123,6 +124,7 @@ namespace Avalonia.Controls.UnitTests.Selection
             target.WritableSelectedItems = null;
 
             Assert.Empty(target.SelectedIndexes);
+            Assert.Empty(target.WritableSelectedItems);
         }
 
         [Fact]
@@ -182,6 +184,7 @@ namespace Avalonia.Controls.UnitTests.Selection
             target.Source = items;
 
             Assert.Equal(1, target.SelectedIndex);
+            Assert.Equal(new[] { "bar" }, target.WritableSelectedItems);
         }
 
         [Fact]
@@ -203,6 +206,7 @@ namespace Avalonia.Controls.UnitTests.Selection
             items.Reset(new[] { "baz", "foo", "bar" });
 
             Assert.Equal(2, target.SelectedIndex);
+            Assert.Equal(new[] { "bar" }, target.WritableSelectedItems);
         }
 
         [Fact]
@@ -227,6 +231,7 @@ namespace Avalonia.Controls.UnitTests.Selection
 
             Assert.Equal(-1, target.SelectedIndex);
             Assert.Equal(null, target.SelectedItem);
+            Assert.Empty(target.WritableSelectedItems);
 
             Assert.Contains(nameof(target.SelectedIndex), changed);
             Assert.Contains(nameof(target.SelectedItem), changed);
@@ -246,6 +251,7 @@ namespace Avalonia.Controls.UnitTests.Selection
 
             Assert.Equal("foo", target.SelectedItem);
             Assert.Equal(1, target.SelectedIndex);
+            Assert.Equal(new[] { "foo" }, target.WritableSelectedItems);
         }
 
         [Fact]
@@ -257,6 +263,7 @@ namespace Avalonia.Controls.UnitTests.Selection
             target.Source = new[] { "baz", "foo", "bar" };
 
             Assert.Equal(2, target.SelectedIndex);
+            Assert.Equal(new[] { "bar" }, target.WritableSelectedItems);
         }
 
         private static InternalSelectionModel CreateTarget(