VirtualizingPanel.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics.CodeAnalysis;
  6. using Avalonia.Controls.Utils;
  7. using Avalonia.Input;
  8. namespace Avalonia.Controls
  9. {
  10. /// <summary>
  11. /// Base class for panels that can be used to virtualize items.
  12. /// </summary>
  13. public abstract class VirtualizingPanel : Panel, INavigableContainer
  14. {
  15. private ItemsControl? _itemsControl;
  16. protected ItemsControl? ItemsControl
  17. {
  18. get => _itemsControl;
  19. private set
  20. {
  21. if (_itemsControl != value)
  22. {
  23. var oldValue = _itemsControl;
  24. _itemsControl= value;
  25. OnItemsControlChanged(oldValue);
  26. }
  27. }
  28. }
  29. IInputElement? INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
  30. {
  31. return GetControl(direction, from, wrap);
  32. }
  33. /// <summary>
  34. /// Scrolls the specified item into view.
  35. /// </summary>
  36. /// <param name="index">The index of the item.</param>
  37. /// <returns>
  38. /// The element with the specified index, or null if the element could not be brought into view.
  39. /// </returns>
  40. protected internal abstract Control? ScrollIntoView(int index);
  41. /// <summary>
  42. /// Returns the container for the item at the specified index.
  43. /// </summary>
  44. /// <param name="index">The index of the item to retrieve.</param>
  45. /// <returns>
  46. /// The container for the item at the specified index within the item collection, if the
  47. /// item is realized; otherwise, null.
  48. /// </returns>
  49. protected internal abstract Control? ContainerFromIndex(int index);
  50. /// <summary>
  51. /// Returns the index to the item that has the specified realized container.
  52. /// </summary>
  53. /// <param name="container">The generated container to retrieve the item index for.</param>
  54. /// <returns>
  55. /// The index to the item that corresponds to the specified realized container, or -1 if
  56. /// <paramref name="container"/> is not found.
  57. /// </returns>
  58. protected internal abstract int IndexFromContainer(Control container);
  59. /// <summary>
  60. /// Gets the currently realized containers.
  61. /// </summary>
  62. protected internal abstract IEnumerable<Control>? GetRealizedContainers();
  63. /// <summary>
  64. /// Gets the next control in the specified direction.
  65. /// </summary>
  66. /// <param name="direction">The movement direction.</param>
  67. /// <param name="from">The control from which movement begins.</param>
  68. /// <param name="wrap">Whether to wrap around when the first or last item is reached.</param>
  69. /// <returns>The control.</returns>
  70. protected abstract IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap);
  71. /// <summary>
  72. /// Called when the <see cref="ItemsControl"/> that owns the panel changes.
  73. /// </summary>
  74. /// <param name="oldValue">
  75. /// The old value of the <see cref="ItemsControl"/> property.
  76. /// </param>
  77. protected virtual void OnItemsControlChanged(ItemsControl? oldValue)
  78. {
  79. }
  80. /// <summary>
  81. /// Called when the <see cref="ItemsControl.Items"/> collection of the owner
  82. /// <see cref="ItemsControl"/> changes.
  83. /// </summary>
  84. /// <param name="items">The items.</param>
  85. /// <param name="e">The event args.</param>
  86. /// <remarks>
  87. /// This method is called a <see cref="INotifyCollectionChanged"/> event is raised by
  88. /// the items, or when the <see cref="ItemsControl.Items"/> property is assigned a
  89. /// new collection, in which case the <see cref="NotifyCollectionChangedAction"/> will
  90. /// be <see cref="NotifyCollectionChangedAction.Reset"/>.
  91. /// </remarks>
  92. protected virtual void OnItemsChanged(IList items, NotifyCollectionChangedEventArgs e)
  93. {
  94. }
  95. /// <summary>
  96. /// Adds the specified <see cref="Control"/> to the <see cref="Panel.Children"/> collection
  97. /// of a <see cref="VirtualizingPanel"/> element.
  98. /// </summary>
  99. /// <param name="control">The control to add to the collection.</param>
  100. protected void AddInternalChild(Control control)
  101. {
  102. var itemsControl = EnsureItemsControl();
  103. itemsControl.AddLogicalChild(control);
  104. Children.Add(control);
  105. }
  106. /// <summary>
  107. /// Adds the specified <see cref="Control"/> to the <see cref="Panel.Children"/> collection
  108. /// of a <see cref="VirtualizingPanel"/> element at the specified index position.
  109. /// </summary>
  110. /// <param name="index">
  111. /// The index position within the collection at which the child element is inserted.
  112. /// </param>
  113. /// <param name="control">The control to add to the collection.</param>
  114. protected void InsertInternalChild(int index, Control control)
  115. {
  116. var itemsControl = EnsureItemsControl();
  117. itemsControl.AddLogicalChild(control);
  118. Children.Insert(index, control);
  119. }
  120. /// <summary>
  121. /// Removes child elements from the <see cref="Panel.Children"/> collection.
  122. /// </summary>
  123. /// <param name="index">
  124. /// The beginning index position within the collection at which the first child element is
  125. /// removed.
  126. /// </param>
  127. /// <param name="count">The number of child elements to remove.</param>
  128. protected void RemoveInternalChildRange(int index, int count)
  129. {
  130. var itemsControl = EnsureItemsControl();
  131. for (var i = 0; i < count; ++i)
  132. {
  133. var c = Children[i];
  134. itemsControl.RemoveLogicalChild(c);
  135. }
  136. Children.RemoveRange(index, count);
  137. }
  138. internal void Attach(ItemsControl itemsControl)
  139. {
  140. if (ItemsControl is not null)
  141. throw new InvalidOperationException("The VirtualizingPanel is already attached to an ItemsControl");
  142. ItemsControl = itemsControl;
  143. ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
  144. if (ItemsControl.Items is INotifyCollectionChanged incc)
  145. incc.CollectionChanged += OnItemsControlItemsChanged;
  146. }
  147. internal void Detach()
  148. {
  149. var itemsControl = EnsureItemsControl();
  150. itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
  151. if (itemsControl.Items is INotifyCollectionChanged incc)
  152. incc.CollectionChanged -= OnItemsControlItemsChanged;
  153. ItemsControl = null;
  154. Children.Clear();
  155. }
  156. private ItemsControl EnsureItemsControl()
  157. {
  158. if (ItemsControl is null)
  159. ThrowNotAttached();
  160. return ItemsControl;
  161. }
  162. private protected virtual void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
  163. {
  164. if (e.Property == ItemsControl.ItemsProperty)
  165. {
  166. if (e.OldValue is INotifyCollectionChanged inccOld)
  167. inccOld.CollectionChanged -= OnItemsControlItemsChanged;
  168. OnItemsControlItemsChanged(null, CollectionUtils.ResetEventArgs);
  169. if (e.NewValue is INotifyCollectionChanged inccNew)
  170. inccNew.CollectionChanged += OnItemsControlItemsChanged;
  171. }
  172. }
  173. private void OnItemsControlItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
  174. {
  175. if (_itemsControl?.Items is IList items)
  176. OnItemsChanged(items, e);
  177. }
  178. [DoesNotReturn]
  179. private static void ThrowNotAttached()
  180. {
  181. throw new InvalidOperationException("The VirtualizingPanel does not belong to an ItemsControl.");
  182. }
  183. }
  184. }