From 5416c6028801b23f5660f32a7d0f098e7b71d3c2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 17 May 2023 16:01:42 +0200 Subject: [PATCH 1/3] update sample for simulate issue --- .../ViewModels/ListBoxPageViewModel.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index 7f32536b11..9c30992624 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -21,7 +21,7 @@ namespace ControlCatalog.ViewModels public ListBoxPageViewModel() { Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); - + Selection = new SelectionModel(); Selection.Select(1); @@ -34,7 +34,13 @@ namespace ControlCatalog.ViewModels (t ? Avalonia.Controls.SelectionMode.Toggle : 0) | (a ? Avalonia.Controls.SelectionMode.AlwaysSelected : 0)); - AddItemCommand = MiniCommand.Create(() => Items.Add(GenerateItem())); + AddItemCommand = MiniCommand.Create(() => + { + var item = GenerateItem(); + Items.Add(item); + Selection.Clear(); + Selection.Select(Items.Count - 1); + }); RemoveItemCommand = MiniCommand.Create(() => { @@ -96,7 +102,7 @@ namespace ControlCatalog.ViewModels public MiniCommand RemoveItemCommand { get; } public MiniCommand SelectRandomItemCommand { get; } - private ItemModel GenerateItem() => new ItemModel(_counter ++); + private ItemModel GenerateItem() => new ItemModel(_counter++); } /// From 74412481d8a8a5b74bf3354c632294c080c9a447 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 17 May 2023 16:02:27 +0200 Subject: [PATCH 2/3] fix: Issue #6263 --- .../Primitives/SelectingItemsControl.cs | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index af82a89517..03c6eebf13 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -5,9 +5,7 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Xml.Linq; using Avalonia.Controls.Selection; -using Avalonia.Controls.Utils; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; @@ -171,7 +169,7 @@ namespace Avalonia.Controls.Primitives /// public event EventHandler? SelectionChanged { - add => AddHandler(SelectionChangedEvent, value); + add => AddHandler(SelectionChangedEvent, value); remove => RemoveHandler(SelectionChangedEvent, value); } @@ -369,7 +367,7 @@ namespace Avalonia.Controls.Primitives /// public bool WrapSelection { - get => GetValue(WrapSelectionProperty); + get => GetValue(WrapSelectionProperty); set => SetValue(WrapSelectionProperty, value); } @@ -382,7 +380,7 @@ namespace Avalonia.Controls.Primitives /// protected SelectionMode SelectionMode { - get => GetValue(SelectionModeProperty); + get => GetValue(SelectionModeProperty); set => SetValue(SelectionModeProperty, value); } @@ -465,7 +463,10 @@ namespace Avalonia.Controls.Primitives protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - AutoScrollToSelectedItemIfNecessary(); + if (Selection?.AnchorIndex is int index) + { + AutoScrollToSelectedItemIfNecessary(index); + } } /// @@ -476,7 +477,10 @@ namespace Avalonia.Controls.Primitives void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e) { LayoutUpdated -= ExecuteScrollWhenLayoutUpdated; - AutoScrollToSelectedItemIfNecessary(); + if (Selection?.AnchorIndex is int index) + { + AutoScrollToSelectedItemIfNecessary(index); + } } if (AutoScrollToSelectedItem) @@ -657,7 +661,10 @@ namespace Avalonia.Controls.Primitives if (change.Property == AutoScrollToSelectedItemProperty) { - AutoScrollToSelectedItemIfNecessary(); + if (Selection?.AnchorIndex is int index) + { + AutoScrollToSelectedItemIfNecessary(index); + } } else if (change.Property == SelectionModeProperty && _selection is object) { @@ -916,8 +923,11 @@ namespace Avalonia.Controls.Primitives if (e.PropertyName == nameof(ISelectionModel.AnchorIndex)) { _hasScrolledToSelectedItem = false; - KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(Selection.AnchorIndex)); - AutoScrollToSelectedItemIfNecessary(); + if (Selection?.AnchorIndex is int index) + { + KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(index)); + AutoScrollToSelectedItemIfNecessary(index); + } } else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex) { @@ -1045,7 +1055,7 @@ namespace Avalonia.Controls.Primitives return value; } else - { + { return AvaloniaProperty.UnsetValue; } } @@ -1103,16 +1113,19 @@ namespace Avalonia.Controls.Primitives } } - private void AutoScrollToSelectedItemIfNecessary() + private void AutoScrollToSelectedItemIfNecessary(int anchorIndex) { if (AutoScrollToSelectedItem && !_hasScrolledToSelectedItem && Presenter is object && - Selection.AnchorIndex >= 0 && + anchorIndex >= 0 && IsAttachedToVisualTree) { - ScrollIntoView(Selection.AnchorIndex); - _hasScrolledToSelectedItem = true; + Dispatcher.UIThread.Post(state => + { + ScrollIntoView((int)state!); + _hasScrolledToSelectedItem = true; + }, anchorIndex); } } From 176a6a83c5e1d3b1be5013713653a78d5869661c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 17 May 2023 17:26:32 +0200 Subject: [PATCH 3/3] fix(test): call Threading.Dispatcher.UIThread.RunJobs(); --- tests/Avalonia.Controls.UnitTests/ListBoxTests.cs | 12 ++++++++++++ .../Primitives/SelectingItemsControlTests.cs | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 72f476a3b0..732f888e49 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -431,6 +431,8 @@ namespace Avalonia.Controls.UnitTests items.Remove("1"); lm.ExecuteLayoutPass(); + Threading.Dispatcher.UIThread.RunJobs(); + Assert.Equal("30", target.ContainerFromIndex(items.Count - 1).DataContext); Assert.Equal("29", target.ContainerFromIndex(items.Count - 2).DataContext); Assert.Equal("28", target.ContainerFromIndex(items.Count - 3).DataContext); @@ -456,8 +458,13 @@ namespace Avalonia.Controls.UnitTests Prepare(target); + Threading.Dispatcher.UIThread.RunJobs(); + // First an item that is not index 0 must be selected. _mouse.Click(target.Presenter.Panel.Children[1]); + + Threading.Dispatcher.UIThread.RunJobs(); + Assert.Equal(1, target.Selection.AnchorIndex); // We're going to be clicking on item 9. @@ -470,6 +477,7 @@ namespace Avalonia.Controls.UnitTests // into view due to SelectionMode.AlwaysSelected. target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => { + Assert.Same(item, e.TargetObject); ++raised; }); @@ -477,6 +485,8 @@ namespace Avalonia.Controls.UnitTests // Click item 9. _mouse.Click(item); + Threading.Dispatcher.UIThread.RunJobs(); + Assert.Equal(1, raised); } } @@ -743,6 +753,8 @@ namespace Avalonia.Controls.UnitTests items.Reverse(); Layout(target); + Threading.Dispatcher.UIThread.RunJobs(); + realized = target.GetRealizedContainers() .Cast() .Select(x => (string)x.DataContext) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 51745e1687..7ce9992313 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1536,7 +1536,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Prepare(target); target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true); target.SelectedIndex = 2; - + Threading.Dispatcher.UIThread.RunJobs(); Assert.True(raised); } @@ -1561,7 +1561,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true); target.SelectedIndex = 2; Prepare(target); - + Threading.Dispatcher.UIThread.RunJobs(); Assert.True(raised); } @@ -1632,7 +1632,7 @@ namespace Avalonia.Controls.UnitTests.Primitives root.Child = null; target.SelectedIndex = 1; root.Child = target; - + Threading.Dispatcher.UIThread.RunJobs(); Assert.True(raised); } @@ -1689,11 +1689,11 @@ namespace Avalonia.Controls.UnitTests.Primitives var raised = false; target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true); target.SelectedIndex = 2; - + Threading.Dispatcher.UIThread.RunJobs(); Assert.False(raised); target.AutoScrollToSelectedItem = true; - + Threading.Dispatcher.UIThread.RunJobs(); Assert.True(raised); }