Browse Source

address review

pull/20798/head
timunie 5 months ago
parent
commit
086bd59b1d
  1. 78
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  2. 45
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs

78
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -478,16 +478,13 @@ namespace Avalonia.Controls.Primitives
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e)
{
LayoutUpdated -= ExecuteScrollWhenLayoutUpdated;
AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
}
if (AutoScrollToSelectedItem) if (AutoScrollToSelectedItem)
{ {
LayoutUpdated += ExecuteScrollWhenLayoutUpdated; Dispatcher.UIThread.Post(static state =>
{
var control = (SelectingItemsControl)state!;
control.AutoScrollToSelectedItemIfNecessary(control.GetAnchorIndex());
}, this);
} }
} }
@ -535,7 +532,11 @@ namespace Avalonia.Controls.Primitives
if (Selection.AnchorIndex == index) if (Selection.AnchorIndex == index)
KeyboardNavigation.SetTabOnceActiveElement(this, container); KeyboardNavigation.SetTabOnceActiveElement(this, container);
if (AlwaysSelected && index == SelectedIndex && (!container.IsVisible || !container.IsEnabled)) if (AlwaysSelected && SelectedIndex == -1 && container is { IsVisible: true, IsEnabled: true })
{
SelectedIndex = index;
}
else if (AlwaysSelected && index == SelectedIndex && (!container.IsVisible || !container.IsEnabled))
{ {
MoveSelectionToFirstVisibleAndEnabledItem(); MoveSelectionToFirstVisibleAndEnabledItem();
} }
@ -1158,21 +1159,25 @@ namespace Avalonia.Controls.Primitives
} }
private int? _pendingAutoScrollAnchorIndex; private int? _pendingAutoScrollAnchorIndex;
private void AutoScrollToSelectedItemIfNecessary(int anchorIndex) private void AutoScrollToSelectedItemIfNecessary(int anchorIndex)
{ {
if (!(AutoScrollToSelectedItem && !_hasScrolledToSelectedItem && Presenter != null && anchorIndex >= 0 && IsAttachedToVisualTree)) if (!(AutoScrollToSelectedItem && !_hasScrolledToSelectedItem && Presenter != null && anchorIndex >= 0 && IsAttachedToVisualTree))
{
ClearPendingAutoScroll();
return; return;
}
if (!IsEffectivelyVisible) if (!IsEffectivelyVisible)
{ {
// Defer scroll until visible // Defer scroll until the control becomes effectively visible.
_pendingAutoScrollAnchorIndex = anchorIndex; _pendingAutoScrollAnchorIndex = anchorIndex;
LayoutUpdated -= OnLayoutUpdatedForAutoScroll; // Avoid duplicate handlers IsEffectivelyVisibleChanged -= OnIsEffectivelyVisibleChangedForAutoScroll;
LayoutUpdated += OnLayoutUpdatedForAutoScroll; IsEffectivelyVisibleChanged += OnIsEffectivelyVisibleChangedForAutoScroll;
return; return;
} }
// If already visible, scroll immediately ClearPendingAutoScroll();
Dispatcher.UIThread.Post(state => Dispatcher.UIThread.Post(state =>
{ {
ScrollIntoView((int)state!); ScrollIntoView((int)state!);
@ -1180,19 +1185,25 @@ namespace Avalonia.Controls.Primitives
}, anchorIndex); }, anchorIndex);
} }
private void OnLayoutUpdatedForAutoScroll(object? sender, EventArgs e) private void OnIsEffectivelyVisibleChangedForAutoScroll(object? sender, EventArgs e)
{ {
if (_pendingAutoScrollAnchorIndex != null && IsEffectivelyVisible) if (!IsEffectivelyVisible || _pendingAutoScrollAnchorIndex is not { } anchorIndex)
{ {
int anchorIndex = _pendingAutoScrollAnchorIndex.Value; return;
LayoutUpdated -= OnLayoutUpdatedForAutoScroll;
_pendingAutoScrollAnchorIndex = null;
Dispatcher.UIThread.Post(state =>
{
ScrollIntoView((int)state!);
_hasScrolledToSelectedItem = true;
}, anchorIndex);
} }
ClearPendingAutoScroll();
Dispatcher.UIThread.Post(state =>
{
ScrollIntoView((int)state!);
_hasScrolledToSelectedItem = true;
}, anchorIndex);
}
private void ClearPendingAutoScroll()
{
_pendingAutoScrollAnchorIndex = null;
IsEffectivelyVisibleChanged -= OnIsEffectivelyVisibleChangedForAutoScroll;
} }
/// <summary> /// <summary>
@ -1256,19 +1267,19 @@ namespace Avalonia.Controls.Primitives
{ {
if (container is { IsVisible: true, IsEnabled: true }) if (container is { IsVisible: true, IsEnabled: true })
return i; return i;
continue;
} }
else
var item = ItemsView[i];
if (item is Visual v)
{ {
var item = ItemsView[i]; if (v.IsVisible && (v is not Control c || c.IsEnabled))
if (item is Visual v)
{
if (v.IsVisible && (v is not Control c || c.IsEnabled))
return i;
}
else if (item is not null)
{
return i; return i;
} }
else if (item is not null)
{
return i;
} }
} }
@ -1533,3 +1544,4 @@ namespace Avalonia.Controls.Primitives
} }
} }
} }

45
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs

@ -162,6 +162,51 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
} }
[Fact]
public void AutoScrollToSelectedItem_Should_Work_When_Ancestor_Becomes_Visible()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var items = Enumerable.Range(0, 100).Select(i => $"Item {i}").ToList();
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((_, _) => new TextBlock { Height = 50 }),
Height = 100,
ItemsPanel = new FuncTemplate<Panel?>(() => new VirtualizingStackPanel { CacheLength = 0 }),
AutoScrollToSelectedItem = true,
};
target.Width = target.Height = 100;
var host = new StackPanel
{
IsVisible = false,
Children =
{
target,
},
};
var root = new TestRoot(host);
root.LayoutManager.ExecuteInitialLayoutPass();
target.SelectedIndex = 50;
Assert.False(target.IsEffectivelyVisible);
host.IsVisible = true;
root.LayoutManager.ExecuteLayoutPass();
Dispatcher.UIThread.RunJobs(null, TestContext.Current.CancellationToken);
root.LayoutManager.ExecuteLayoutPass();
var scrollViewer = (ScrollViewer)target.VisualChildren[0];
var offset = scrollViewer.Offset.Y;
Assert.InRange(offset, 2400, 2500);
}
}
private static FuncControlTemplate Template() private static FuncControlTemplate Template()
{ {
return new FuncControlTemplate<SelectingItemsControl>((control, scope) => return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>

Loading…
Cancel
Save