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.");
}
}
}