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);
void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e)
{
LayoutUpdated -= ExecuteScrollWhenLayoutUpdated;
AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
}
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)
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();
}
@ -1158,21 +1159,25 @@ namespace Avalonia.Controls.Primitives
}
private int? _pendingAutoScrollAnchorIndex;
private void AutoScrollToSelectedItemIfNecessary(int anchorIndex)
{
if (!(AutoScrollToSelectedItem && !_hasScrolledToSelectedItem && Presenter != null && anchorIndex >= 0 && IsAttachedToVisualTree))
{
ClearPendingAutoScroll();
return;
}
if (!IsEffectivelyVisible)
{
// Defer scroll until visible
// Defer scroll until the control becomes effectively visible.
_pendingAutoScrollAnchorIndex = anchorIndex;
LayoutUpdated -= OnLayoutUpdatedForAutoScroll; // Avoid duplicate handlers
LayoutUpdated += OnLayoutUpdatedForAutoScroll;
IsEffectivelyVisibleChanged -= OnIsEffectivelyVisibleChangedForAutoScroll;
IsEffectivelyVisibleChanged += OnIsEffectivelyVisibleChangedForAutoScroll;
return;
}
// If already visible, scroll immediately
ClearPendingAutoScroll();
Dispatcher.UIThread.Post(state =>
{
ScrollIntoView((int)state!);
@ -1180,19 +1185,25 @@ namespace Avalonia.Controls.Primitives
}, 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;
LayoutUpdated -= OnLayoutUpdatedForAutoScroll;
_pendingAutoScrollAnchorIndex = null;
Dispatcher.UIThread.Post(state =>
{
ScrollIntoView((int)state!);
_hasScrolledToSelectedItem = true;
}, anchorIndex);
return;
}
ClearPendingAutoScroll();
Dispatcher.UIThread.Post(state =>
{
ScrollIntoView((int)state!);
_hasScrolledToSelectedItem = true;
}, anchorIndex);
}
private void ClearPendingAutoScroll()
{
_pendingAutoScrollAnchorIndex = null;
IsEffectivelyVisibleChanged -= OnIsEffectivelyVisibleChangedForAutoScroll;
}
/// <summary>
@ -1256,19 +1267,19 @@ namespace Avalonia.Controls.Primitives
{
if (container is { IsVisible: true, IsEnabled: true })
return i;
continue;
}
else
var item = ItemsView[i];
if (item is Visual v)
{
var item = ItemsView[i];
if (item is Visual v)
{
if (v.IsVisible && (v is not Control c || c.IsEnabled))
return i;
}
else if (item is not null)
{
if (v.IsVisible && (v is not Control c || c.IsEnabled))
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()
{
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>

Loading…
Cancel
Save