Browse Source

Merge pull request #1962 from donandren/issues/1395

unit test for Listbox OutOfRangeException issue #1395
Steven Kirk 7 years ago
parent
commit
06659ae445

+ 23 - 1
src/Avalonia.Base/Utilities/MathUtilities.cs

@@ -1,7 +1,6 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-
 namespace Avalonia.Utilities
 {
     /// <summary>
@@ -31,5 +30,28 @@ namespace Avalonia.Utilities
                 return val;
             }
         }
+
+        /// <summary>
+        /// Clamps a value between a minimum and maximum value.
+        /// </summary>
+        /// <param name="val">The value.</param>
+        /// <param name="min">The minimum value.</param>
+        /// <param name="max">The maximum value.</param>
+        /// <returns>The clamped value.</returns>
+        public static int Clamp(int val, int min, int max)
+        {
+            if (val < min)
+            {
+                return min;
+            }
+            else if (val > max)
+            {
+                return max;
+            }
+            else
+            {
+                return val;
+            }
+        }
     }
 }

+ 21 - 3
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -76,9 +76,23 @@ namespace Avalonia.Controls.Presenters
                         var firstIndex = ItemCount - panel.Children.Count;
                         RecycleContainersForMove(firstIndex - FirstIndex);
 
-                        panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
-                            panel.Children[0].Bounds.Height :
-                            panel.Children[0].Bounds.Width;
+                        double pixelOffset;
+                        var child = panel.Children[0];
+
+                        if (child.IsArrangeValid)
+                        {
+                            pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
+                                                    child.Bounds.Height :
+                                                    child.Bounds.Width;
+                        }
+                        else
+                        {
+                            pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
+                                                    child.DesiredSize.Height :
+                                                    child.DesiredSize.Width;
+                        }
+
+                        panel.PixelOffset = pixelOffset;
                     }
                 }
             }
@@ -402,6 +416,10 @@ namespace Avalonia.Controls.Presenters
             var panel = VirtualizingPanel;
             var generator = Owner.ItemContainerGenerator;
             var selector = Owner.MemberSelector;
+
+            //validate delta it should never overflow last index or generate index < 0 
+            delta = MathUtilities.Clamp(delta, -FirstIndex, ItemCount - FirstIndex - panel.Children.Count);
+
             var sign = delta < 0 ? -1 : 1;
             var count = Math.Min(Math.Abs(delta), panel.Children.Count);
             var move = count < panel.Children.Count;

+ 35 - 0
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -196,6 +196,41 @@ namespace Avalonia.Controls.UnitTests
                 target.Presenter.Panel.Children.Cast<ListBoxItem>().Select(x => (string)x.Content));
         }
 
+
+        [Fact]
+        public void ListBox_After_Scroll_IndexOutOfRangeException_Shouldnt_Be_Thrown()
+        {
+            var items = Enumerable.Range(0, 11).Select(x => $"{x}").ToArray();
+
+            var target = new ListBox
+            {
+                Template = ListBoxTemplate(),
+                Items = items,
+                ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 11 })
+            };
+
+            Prepare(target);
+
+            var panel = target.Presenter.Panel as IVirtualizingPanel;
+
+            var listBoxItems = panel.Children.OfType<ListBoxItem>();
+
+            //virtualization should have created exactly 10 items
+            Assert.Equal(10, listBoxItems.Count());
+            Assert.Equal("0", listBoxItems.First().DataContext);
+            Assert.Equal("9", listBoxItems.Last().DataContext);
+
+            //instead pixeloffset > 0 there could be pretty complex sequence for repro
+            //it involves add/remove/scroll to end multiple actions
+            //which i can't find so far :(, but this is the simplest way to add it to unit test
+            panel.PixelOffset = 1;
+
+            //here scroll to end -> IndexOutOfRangeException is thrown
+            target.Scroll.Offset = new Vector(0, 2);
+
+            Assert.True(true);
+        }
+
         private FuncControlTemplate ListBoxTemplate()
         {
             return new FuncControlTemplate<ListBox>(parent =>