Browse Source

Merge pull request #11752 from AvaloniaUI/emmauss/scroll-snappoints-update

IScrollSnapPointInfo improvements
pull/11811/head
Steven Kirk 3 years ago
committed by GitHub
parent
commit
53d14ba3bd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 86
      src/Avalonia.Controls/ItemsControl.cs
  2. 120
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  3. 73
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  4. 156
      src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
  5. 103
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  6. 2
      src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml
  7. 2
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  8. 2
      src/Avalonia.Themes.Simple/Controls/ItemsControl.xaml
  9. 2
      src/Avalonia.Themes.Simple/Controls/ListBox.xaml
  10. 51
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

86
src/Avalonia.Controls/ItemsControl.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls
/// Displays a collection of items.
/// </summary>
[PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IChildIndexProvider, IScrollSnapPointsInfo
public class ItemsControl : TemplatedControl, IChildIndexProvider
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
@ -67,18 +67,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
@ -249,64 +237,6 @@ namespace Avalonia.Controls
/// </remarks>
public event EventHandler<ContainerClearingEventArgs>? ContainerClearing;
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{
add
{
if (_itemsPresenter != null)
{
_itemsPresenter.HorizontalSnapPointsChanged += value;
}
}
remove
{
if (_itemsPresenter != null)
{
_itemsPresenter.HorizontalSnapPointsChanged -= value;
}
}
}
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged
{
add
{
if (_itemsPresenter != null)
{
_itemsPresenter.VerticalSnapPointsChanged += value;
}
}
remove
{
if (_itemsPresenter != null)
{
_itemsPresenter.VerticalSnapPointsChanged -= value;
}
}
}
/// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="ItemsControl"/> are equidistant from each other.
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get => GetValue(AreHorizontalSnapPointsRegularProperty);
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
/// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="ItemsControl"/> are equidistant from each other.
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get => GetValue(AreVerticalSnapPointsRegularProperty);
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
/// <summary>
/// Gets a default recycle key that can be used when an <see cref="ItemsControl"/> supports
/// a single container type.
@ -896,19 +826,5 @@ namespace Avalonia.Controls
count = ItemsView.Count;
return true;
}
/// <inheritdoc />
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List<double>();
}
/// <inheritdoc />
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
offset = 0;
return _itemsPresenter?.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset) ?? 0;
}
}
}

120
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Presents items inside an <see cref="Avalonia.Controls.ItemsControl"/>.
/// </summary>
public class ItemsPresenter : Control, ILogicalScrollable, IScrollSnapPointsInfo
public class ItemsPresenter : Control, ILogicalScrollable
{
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
@ -21,37 +21,8 @@ namespace Avalonia.Controls.Presenters
private PanelContainerGenerator? _generator;
private ILogicalScrollable? _logicalScrollable;
private IScrollSnapPointsInfo? _scrollSnapPointsInfo;
private EventHandler? _scrollInvalidated;
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreHorizontalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
/// </summary>
public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
AvaloniaProperty.Register<ItemsPresenter, bool>(nameof(AreVerticalSnapPointsRegular));
/// <summary>
/// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(HorizontalSnapPointsChanged),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="VerticalSnapPointsChanged"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
RoutedEvent.Register<ItemsPresenter, RoutedEventArgs>(
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
event EventHandler? ILogicalScrollable.ScrollInvalidated
{
add => _scrollInvalidated += value;
@ -107,47 +78,12 @@ namespace Avalonia.Controls.Presenters
}
}
/// <summary>
/// Occurs when the measurements for horizontal snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? HorizontalSnapPointsChanged
{
add => AddHandler(HorizontalSnapPointsChangedEvent, value);
remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
}
/// <summary>
/// Occurs when the measurements for vertical snap points change.
/// </summary>
public event EventHandler<RoutedEventArgs>? VerticalSnapPointsChanged
{
add => AddHandler(VerticalSnapPointsChangedEvent, value);
remove => RemoveHandler(VerticalSnapPointsChangedEvent, value);
}
bool ILogicalScrollable.IsLogicalScrollEnabled => _logicalScrollable?.IsLogicalScrollEnabled ?? false;
Size ILogicalScrollable.ScrollSize => _logicalScrollable?.ScrollSize ?? default;
Size ILogicalScrollable.PageScrollSize => _logicalScrollable?.PageScrollSize ?? default;
Size IScrollable.Extent => _logicalScrollable?.Extent ?? default;
Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default;
/// <summary>
/// Gets or sets whether the horizontal snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
}
/// <summary>
/// Gets or sets whether the vertical snap points for the <see cref="ItemsPresenter"/> are equidistant from each other.
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get { return GetValue(AreVerticalSnapPointsRegularProperty); }
set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
}
public override sealed void ApplyTemplate()
{
@ -167,36 +103,14 @@ namespace Avalonia.Controls.Presenters
Panel.TemplatedParent = TemplatedParent;
Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);
if (_scrollSnapPointsInfo != null)
{
_scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular;
_scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular;
}
if (Panel is VirtualizingPanel v)
v.Attach(ItemsControl);
else
CreateSimplePanelGenerator();
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) =>
{
e.RoutedEvent = VerticalSnapPointsChangedEvent;
RaiseEvent(e);
};
scrollSnapPointsInfo.HorizontalSnapPointsChanged += (s, e) =>
{
e.RoutedEvent = HorizontalSnapPointsChangedEvent;
RaiseEvent(e);
};
}
_logicalScrollable = Panel as ILogicalScrollable;
if (_logicalScrollable is not null)
@ -240,16 +154,6 @@ namespace Avalonia.Controls.Presenters
ResetState();
InvalidateMeasure();
}
else if(change.Property == AreHorizontalSnapPointsRegularProperty)
{
if (_scrollSnapPointsInfo != null)
_scrollSnapPointsInfo.AreHorizontalSnapPointsRegular = AreHorizontalSnapPointsRegular;
}
else if (change.Property == AreVerticalSnapPointsRegularProperty)
{
if (_scrollSnapPointsInfo != null)
_scrollSnapPointsInfo.AreVerticalSnapPointsRegular = AreVerticalSnapPointsRegular;
}
}
internal void Refresh()
@ -303,27 +207,5 @@ namespace Avalonia.Controls.Presenters
}
private void OnLogicalScrollInvalidated(object? sender, EventArgs e) => _scrollInvalidated?.Invoke(this, e);
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
return scrollSnapPointsInfo.GetIrregularSnapPoints(orientation, snapPointsAlignment);
}
return new List<double>();
}
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
return scrollSnapPointsInfo.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset);
}
offset = 0;
return 0;
}
}
}

73
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -7,6 +7,7 @@ using Avalonia.Input.GestureRecognizers;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Linq;
using Avalonia.Interactivity;
namespace Avalonia.Controls.Presenters
{
@ -99,6 +100,8 @@ namespace Avalonia.Controls.Presenters
private double _horizontalSnapPointOffset;
private CompositeDisposable? _ownerSubscriptions;
private ScrollViewer? _owner;
private IScrollSnapPointsInfo? _scrollSnapPointsInfo;
private bool _isSnapPointsUpdated;
/// <summary>
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
@ -377,6 +380,13 @@ namespace Avalonia.Controls.Presenters
CanVerticallyScroll ? double.PositiveInfinity : availableSize.Height);
Child.Measure(constraint);
if (!_isSnapPointsUpdated)
{
_isSnapPointsUpdated = true;
UpdateSnapPoints();
}
return Child.DesiredSize.Constrain(availableSize);
}
@ -570,7 +580,12 @@ namespace Avalonia.Controls.Presenters
private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e)
{
if (Content is not IScrollSnapPointsInfo)
var scrollable = Content;
if (Content is ItemsControl itemsControl)
scrollable = itemsControl.Presenter?.Panel;
if (scrollable is not IScrollSnapPointsInfo)
return;
if (_scrollGestureSnapPoints == null)
@ -676,22 +691,6 @@ namespace Avalonia.Controls.Presenters
_owner?.SetCurrentValue(OffsetProperty, change.GetNewValue<Vector>());
}
else if (change.Property == ContentProperty)
{
if (change.OldValue is IScrollSnapPointsInfo oldSnapPointsInfo)
{
oldSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
oldSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
}
if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
}
UpdateSnapPoints();
}
else if (change.Property == ChildProperty)
{
ChildChanged(change);
@ -875,7 +874,9 @@ namespace Avalonia.Controls.Presenters
private void UpdateSnapPoints()
{
if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
var scrollable = GetScrollSnapPointsInfo(Content);
if (scrollable is IScrollSnapPointsInfo scrollSnapPointsInfo)
{
_areVerticalSnapPointsRegular = scrollSnapPointsInfo.AreVerticalSnapPointsRegular;
_areHorizontalSnapPointsRegular = scrollSnapPointsInfo.AreHorizontalSnapPointsRegular;
@ -910,7 +911,9 @@ namespace Avalonia.Controls.Presenters
private Vector SnapOffset(Vector offset)
{
if(Content is not IScrollSnapPointsInfo)
var scrollable = GetScrollSnapPointsInfo(Content);
if(scrollable is null)
return offset;
var diff = GetAlignedDiff();
@ -1012,5 +1015,37 @@ namespace Avalonia.Controls.Presenters
}
return snapPoints[Math.Min(point, snapPoints.Count - 1)];
}
private IScrollSnapPointsInfo? GetScrollSnapPointsInfo(object? content)
{
var scrollable = content;
if (Content is ItemsControl itemsControl)
scrollable = itemsControl.Presenter?.Panel;
if (Content is ItemsPresenter itemsPresenter)
scrollable = itemsPresenter.Panel;
var snapPointsInfo = scrollable as IScrollSnapPointsInfo;
if(snapPointsInfo != _scrollSnapPointsInfo)
{
if(_scrollSnapPointsInfo != null)
{
_scrollSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
_scrollSnapPointsInfo.HorizontalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
}
_scrollSnapPointsInfo = snapPointsInfo;
if(_scrollSnapPointsInfo != null)
{
_scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
_scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
}
}
return snapPointsInfo;
}
}
}

156
src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs

@ -0,0 +1,156 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
namespace Avalonia.Controls.Utils
{
internal class VirtualizingSnapPointsList : IReadOnlyList<double>
{
private const int ExtraCount = 2;
private readonly RealizedStackElements _realizedElements;
private readonly Orientation _orientation;
private readonly Orientation _parentOrientation;
private readonly SnapPointsAlignment _snapPointsAlignment;
private readonly double _size;
private readonly int _start = -1;
private readonly int _end;
public VirtualizingSnapPointsList(RealizedStackElements realizedElements, int count, Orientation orientation, Orientation parentOrientation, SnapPointsAlignment snapPointsAlignment, double size)
{
_realizedElements = realizedElements;
_orientation = orientation;
_parentOrientation = parentOrientation;
_snapPointsAlignment = snapPointsAlignment;
_size = size;
if (parentOrientation == orientation)
{
_start = Math.Max(0, _realizedElements.FirstIndex - ExtraCount);
_end = Math.Min(count - 1, _realizedElements.LastIndex + ExtraCount);
}
}
public double this[int index]
{
get
{
if(index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
index += _start;
double snapPoint = 0;
var averageElementSize = _size;
Control? container;
switch (_orientation)
{
case Orientation.Horizontal:
container = _realizedElements.GetElement(index);
if (container != null)
{
switch (_snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = container.Bounds.Left;
break;
case SnapPointsAlignment.Center:
snapPoint = container.Bounds.Center.X;
break;
case SnapPointsAlignment.Far:
snapPoint = container.Bounds.Right;
break;
}
}
else
{
var ind = index;
if (index > _realizedElements.LastIndex)
{
ind -= _realizedElements.LastIndex + 1;
}
snapPoint = ind * averageElementSize;
switch (_snapPointsAlignment)
{
case SnapPointsAlignment.Center:
snapPoint += averageElementSize / 2;
break;
case SnapPointsAlignment.Far:
snapPoint += averageElementSize;
break;
}
if (index > _realizedElements.LastIndex)
{
var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex);
if (lastElement != null)
{
snapPoint += lastElement.Bounds.Right;
}
}
}
break;
case Orientation.Vertical:
container = _realizedElements.GetElement(index);
if (container != null)
{
switch (_snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = container.Bounds.Top;
break;
case SnapPointsAlignment.Center:
snapPoint = container.Bounds.Center.Y;
break;
case SnapPointsAlignment.Far:
snapPoint = container.Bounds.Bottom;
break;
}
}
else
{
var ind = index;
if(index > _realizedElements.LastIndex)
{
ind -= _realizedElements.LastIndex + 1;
}
snapPoint = ind * averageElementSize;
switch (_snapPointsAlignment)
{
case SnapPointsAlignment.Center:
snapPoint += averageElementSize / 2;
break;
case SnapPointsAlignment.Far:
snapPoint += averageElementSize;
break;
}
if (index > _realizedElements.LastIndex)
{
var lastElement = _realizedElements.GetElement(_realizedElements.LastIndex);
if (lastElement != null)
{
snapPoint += lastElement.Bounds.Bottom;
}
}
}
break;
}
return snapPoint;
}
}
public int Count => _parentOrientation != _orientation ? 0 : _end - _start + 1;
public IEnumerator<double> GetEnumerator()
{
for (var i = 0; i < Count; i++)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

103
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -776,107 +776,10 @@ namespace Avalonia.Controls
/// <inheritdoc/>
public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
var snapPoints = new List<double>();
if(_realizedElements == null)
return new List<double>();
switch (orientation)
{
case Orientation.Horizontal:
if (AreHorizontalSnapPointsRegular)
throw new InvalidOperationException();
if (Orientation == Orientation.Horizontal)
{
var averageElementSize = EstimateElementSizeU();
double snapPoint = 0;
for (var i = 0; i < Items.Count; i++)
{
var container = ContainerFromIndex(i);
if (container != null)
{
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = container.Bounds.Left;
break;
case SnapPointsAlignment.Center:
snapPoint = container.Bounds.Center.X;
break;
case SnapPointsAlignment.Far:
snapPoint = container.Bounds.Right;
break;
}
}
else
{
if (snapPoint == 0)
{
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Center:
snapPoint = averageElementSize / 2;
break;
case SnapPointsAlignment.Far:
snapPoint = averageElementSize;
break;
}
}
else
snapPoint += averageElementSize;
}
snapPoints.Add(snapPoint);
}
}
break;
case Orientation.Vertical:
if (AreVerticalSnapPointsRegular)
throw new InvalidOperationException();
if (Orientation == Orientation.Vertical)
{
var averageElementSize = EstimateElementSizeU();
double snapPoint = 0;
for (var i = 0; i < Items.Count; i++)
{
var container = ContainerFromIndex(i);
if (container != null)
{
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Near:
snapPoint = container.Bounds.Top;
break;
case SnapPointsAlignment.Center:
snapPoint = container.Bounds.Center.Y;
break;
case SnapPointsAlignment.Far:
snapPoint = container.Bounds.Bottom;
break;
}
}
else
{
if (snapPoint == 0)
{
switch (snapPointsAlignment)
{
case SnapPointsAlignment.Center:
snapPoint = averageElementSize / 2;
break;
case SnapPointsAlignment.Far:
snapPoint = averageElementSize;
break;
}
}
else
snapPoint += averageElementSize;
}
snapPoints.Add(snapPoint);
}
}
break;
}
return snapPoints;
return new VirtualizingSnapPointsList(_realizedElements, ItemsControl?.ItemsSource?.Count() ?? 0, orientation, Orientation, snapPointsAlignment, EstimateElementSizeU());
}
/// <inheritdoc/>

2
src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml

@ -10,8 +10,6 @@
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}"/>
</Border>
</ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -40,8 +40,6 @@
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}"
Margin="{TemplateBinding Padding}"/>
</ScrollViewer>

2
src/Avalonia.Themes.Simple/Controls/ItemsControl.xaml

@ -11,8 +11,6 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</Border>
</ControlTemplate>

2
src/Avalonia.Themes.Simple/Controls/ListBox.xaml

@ -27,8 +27,6 @@
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Margin="{TemplateBinding Padding}"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</ScrollViewer>
</Border>

51
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@ -1223,6 +1224,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Same(item, root.FocusManager.GetFocusedElement());
}
[Fact]
public void Reads_Only_Realized_Items_From_ItemsSource()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var data = new DataVirtualizingList();
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = data,
};
Prepare(target);
var panel = Assert.IsType<VirtualizingStackPanel>(target.ItemsPanelRoot);
Assert.Equal(0, panel.FirstRealizedIndex);
Assert.Equal(9, panel.LastRealizedIndex);
Assert.Equal(
Enumerable.Range(0, 10).Select(x => $"Item{x}"),
data.GetRealizedItems());
}
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
{
target.RaiseEvent(new KeyEventArgs
@ -1252,5 +1276,32 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
private class DataVirtualizingList : IList
{
private readonly List<string> _inner = new(Enumerable.Repeat<string>(null, 100));
public object this[int index]
{
get => _inner[index] = $"Item{index}";
set => throw new NotSupportedException();
}
public IEnumerable<string> GetRealizedItems() => _inner.Where(x => x is not null);
public bool IsFixedSize => true;
public bool IsReadOnly => true;
public int Count => _inner.Count;
public bool IsSynchronized => false;
public object SyncRoot => this;
public int Add(object value) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public bool Contains(object value) => throw new NotImplementedException();
public void CopyTo(Array array, int index) => throw new NotImplementedException();
public IEnumerator GetEnumerator() => _inner.GetEnumerator();
public int IndexOf(object value) => throw new NotImplementedException();
public void Insert(int index, object value) => throw new NotSupportedException();
public void Remove(object value) => throw new NotSupportedException();
public void RemoveAt(int index) => throw new NotSupportedException();
}
}
}

Loading…
Cancel
Save