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++); } /// diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 0e627c2a37..6382a4a748 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) @@ -625,7 +629,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) { @@ -909,8 +916,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) { @@ -1038,7 +1048,7 @@ namespace Avalonia.Controls.Primitives return value; } else - { + { return AvaloniaProperty.UnsetValue; } } @@ -1096,16 +1106,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); } } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 687338ece0..aeebfabd41 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -432,6 +432,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); @@ -457,8 +459,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. @@ -471,6 +478,7 @@ namespace Avalonia.Controls.UnitTests // into view due to SelectionMode.AlwaysSelected. target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => { + Assert.Same(item, e.TargetObject); ++raised; }); @@ -478,6 +486,8 @@ namespace Avalonia.Controls.UnitTests // Click item 9. _mouse.Click(item); + Threading.Dispatcher.UIThread.RunJobs(); + Assert.Equal(1, raised); } } @@ -744,6 +754,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 6e49fb8ef7..5fba91fafb 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); }