using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.Layout; namespace Avalonia.Controls { /// /// Base class for panels that can be used to virtualize items for an . /// /// /// Panels should implement the abstract members of this class to provide virtualization of /// items in a . Derived panels can manage scrolling by implementing /// or by listening to the /// event. /// /// The methods on the should be used to create, prepare and /// clear containers for items. /// public abstract class VirtualizingPanel : Panel, INavigableContainer { private ItemsControl? _itemsControl; /// /// Gets the for this . /// public ItemContainerGenerator? ItemContainerGenerator => _itemsControl?.ItemContainerGenerator; /// /// Gets the items to display. /// protected IReadOnlyList Items => (IReadOnlyList?)ItemsControl?.ItemsView ?? Array.Empty(); /// /// Gets the that the panel is displaying items for. /// protected ItemsControl? ItemsControl { get => _itemsControl; private set { if (_itemsControl != value) { var oldValue = _itemsControl; _itemsControl= value; OnItemsControlChanged(oldValue); } } } IInputElement? INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap) { return GetControl(direction, from, wrap); } /// /// Scrolls the specified item into view. /// /// The index of the item. /// /// The element with the specified index, or null if the element could not be brought into view. /// protected internal abstract Control? ScrollIntoView(int index); /// /// Returns the container for the item at the specified index. /// /// The index of the item to retrieve. /// /// The container for the item at the specified index within the item collection, if the /// item is realized; otherwise, null. /// /// /// Note for implementors: if the item at the specified index is an ItemIsOwnContainer /// item that has previously been realized, then the item should be returned even if it /// currently falls outside the realized viewport. /// protected internal abstract Control? ContainerFromIndex(int index); /// /// Returns the index to the item that has the specified realized container. /// /// The generated container to retrieve the item index for. /// /// The index to the item that corresponds to the specified realized container, or -1 if /// is not found. /// protected internal abstract int IndexFromContainer(Control container); /// /// Gets the currently realized containers. /// protected internal abstract IEnumerable? GetRealizedContainers(); /// /// Gets the next control in the specified direction. /// /// The movement direction. /// The control from which movement begins. /// Whether to wrap around when the first or last item is reached. /// The control. protected abstract IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap); /// /// Called when the that owns the panel changes. /// /// /// The old value of the property. /// protected virtual void OnItemsControlChanged(ItemsControl? oldValue) { } /// /// Called when the collection of the owner /// changes. /// /// The items. /// The event args. /// /// This method is called a event is raised by /// the items, or when the property is assigned a /// new collection, in which case the will /// be . /// protected virtual void OnItemsChanged(IReadOnlyList items, NotifyCollectionChangedEventArgs e) { } /// /// Adds the specified to the collection /// of a element. /// /// The control to add to the collection. protected void AddInternalChild(Control control) { var itemsControl = EnsureItemsControl(); itemsControl.AddLogicalChild(control); Children.Add(control); } /// /// Adds the specified to the collection /// of a element at the specified index position. /// /// /// The index position within the collection at which the child element is inserted. /// /// The control to add to the collection. protected void InsertInternalChild(int index, Control control) { var itemsControl = EnsureItemsControl(); itemsControl.AddLogicalChild(control); Children.Insert(index, control); } /// /// Removes a child element from the collection. /// /// The child to remove/ protected void RemoveInternalChild(Control child) { var itemsControl = EnsureItemsControl(); itemsControl.RemoveLogicalChild(child); Children.Remove(child); } /// /// Removes child elements from the collection. /// /// /// The beginning index position within the collection at which the first child element is /// removed. /// /// The number of child elements to remove. protected void RemoveInternalChildRange(int index, int count) { var itemsControl = EnsureItemsControl(); for (var i = index; i < count; ++i) { var c = Children[i]; itemsControl.RemoveLogicalChild(c); } Children.RemoveRange(index, count); } private protected override void InvalidateMeasureOnChildrenChanged() { // Don't invalidate measure when children are added or removed: the panel is responsible // for managing its children. } internal void Attach(ItemsControl itemsControl) { if (ItemsControl is not null) throw new InvalidOperationException("The VirtualizingPanel is already attached to an ItemsControl"); ItemsControl = itemsControl; ItemsControl.ItemsView.PostCollectionChanged += OnItemsControlItemsChanged; } internal void Detach() { var itemsControl = EnsureItemsControl(); itemsControl.ItemsView.PostCollectionChanged -= OnItemsControlItemsChanged; ItemsControl = null; Children.Clear(); } internal void Refresh() => OnItemsControlItemsChanged(null, CollectionUtils.ResetEventArgs); private ItemsControl EnsureItemsControl() { if (ItemsControl is null) ThrowNotAttached(); return ItemsControl; } private void OnItemsControlItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) { OnItemsChanged(Items, e); } [DoesNotReturn] private static void ThrowNotAttached() { throw new InvalidOperationException("The VirtualizingPanel does not belong to an ItemsControl."); } } }