Browse Source

Try to handle selections made in CollectionChanged.

There's not much we can do about this except not select invalid indexes.
Steven Kirk 5 years ago
parent
commit
8dfc65d17b

+ 23 - 0
src/Avalonia.Controls/Selection/SelectionModel.cs

@@ -436,6 +436,29 @@ namespace Avalonia.Controls.Selection
             }
         }
 
+        private protected override bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e)
+        {
+            if (!base.IsValidCollectionChange(e))
+            {
+                return false;
+            }
+
+            if (ItemsView is object && e.Action == NotifyCollectionChangedAction.Add)
+            {
+                if (e.NewStartingIndex <= _selectedIndex)
+                {
+                    return _selectedIndex + e.NewItems.Count < ItemsView.Count;
+                }
+
+                if (e.NewStartingIndex <= _anchorIndex)
+                {
+                    return _anchorIndex + e.NewItems.Count < ItemsView.Count;
+                }
+            }
+
+            return true;
+        }
+
         protected override void OnSourceCollectionChangeFinished()
         {
             if (_operation is object)

+ 34 - 0
src/Avalonia.Controls/Selection/SelectionNodeBase.cs

@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
+using System.Linq;
 using Avalonia.Controls.Utils;
 
 #nullable enable
@@ -234,6 +235,11 @@ namespace Avalonia.Controls.Selection
             var shiftIndex = -1;
             List<T>? removed = null;
 
+            if (!IsValidCollectionChange(e))
+            {
+                return;
+            }
+
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
@@ -276,6 +282,34 @@ namespace Avalonia.Controls.Selection
             }
         }
 
+        private protected virtual bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e)
+        {
+            // If the selection is modified in a CollectionChanged handler before the selection
+            // model's CollectionChanged handler has had chance to run then we can end up with
+            // a selected index that refers to the *new* state of the Source intermixed with
+            // indexes that reference an old state of the source.
+            //
+            // There's not much we can do in this situation, so detect whether shifting the
+            // current selected indexes would result in an invalid index in the source, and if
+            // so bail.
+            //
+            // See unit test Handles_Selection_Made_In_CollectionChanged for more details.
+            if (ItemsView is object &&
+                RangesEnabled &&
+                Ranges.Count > 0 &&
+                e.Action == NotifyCollectionChangedAction.Add)
+            {
+                var lastIndex = Ranges.Last().End;
+
+                if (e.NewStartingIndex <= lastIndex)
+                {
+                    return lastIndex + e.NewItems.Count < ItemsView.Count;
+                }
+            }
+
+            return true;
+        }
+
         private protected struct CollectionChangeState
         {
             public int ShiftIndex;