Browse Source

Add scroll anchoring support to ItemsControl.

This necessitated adding another stage to the container preparation lifecycle that will be called after the container is added to the logical and visual trees: `ItemContainerPrepared`. Such a method is going to be necessary anyway if we ever want to expose events for container realization so will probably be worth having regardless.
pull/9677/head
Steven Kirk 4 years ago
parent
commit
6d267f9d20
  1. 20
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  2. 18
      src/Avalonia.Controls/ItemsControl.cs
  3. 1
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  4. 10
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  5. 12
      src/Avalonia.Controls/VirtualizingStackPanel.cs

20
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -56,6 +56,22 @@ namespace Avalonia.Controls.Generators
public void PrepareItemContainer(Control container, object? item, int index) =>
_owner.PrepareItemContainer(container, item, index);
/// <summary>
/// Notifies the <see cref="ItemsControl"/> that a container has been fully prepared to
/// display an item.
/// </summary>
/// <param name="container">The container control.</param>
/// <param name="item">The item being displayed.</param>
/// <param name="index">The index of the item being displayed.</param>
/// <remarks>
/// This method should be called when a container has been fully prepared and added
/// to the logical and visual trees, but may be called before a layout pass has completed.
/// It should be called regardless of the result of
/// <see cref="IsItemItsOwnContainer(Control)"/>.
/// </remarks>
public void ItemContainerPrepared(Control container, object? item, int index) =>
_owner.ItemContainerPrepared(container, item, index);
/// <summary>
/// Called when the index for a container changes due to an insertion or removal in the
/// items collection.
@ -69,8 +85,8 @@ namespace Avalonia.Controls.Generators
/// <summary>
/// Undoes the effects of the <see cref="PrepareItemContainer(Control, object, int)"/> method.
/// </summary>
/// <param name="container">The element that's used to display the specified item.</param>
public void ClearItemContainer(Control container) => _owner.ClearContainerForItemOverride(container);
/// <param name="container">The container control.</param>
public void ClearItemContainer(Control container) => _owner.ClearItemContainer(container);
public Control? ContainerFromIndex(int index) => _owner.ContainerFromIndex(index);
public int IndexFromContainer(Control container) => _owner.IndexFromContainer(container);

18
src/Avalonia.Controls/ItemsControl.cs

@ -90,6 +90,7 @@ namespace Avalonia.Controls
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
private IDataTemplate? _displayMemberItemTemplate;
private Tuple<int, Control>? _containerBeingPrepared;
private ScrollViewer? _scrollViewer;
/// <summary>
/// Initializes a new instance of the <see cref="ItemsControl"/> class.
@ -351,6 +352,12 @@ namespace Avalonia.Controls
/// <returns>true if the item is (or is eligible to be) its own container; otherwise, false.</returns>
protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
}
/// <summary>
/// Handles directional navigation within the <see cref="ItemsControl"/>.
/// </summary>
@ -494,10 +501,15 @@ namespace Avalonia.Controls
container.DataContext = item;
PrepareContainerForItemOverride(container, item, index);
}
internal void ItemContainerPrepared(Control container, object? item, int index)
{
_containerBeingPrepared = new(index, container);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container));
_containerBeingPrepared = null;
_scrollViewer?.RegisterAnchorCandidate(container);
}
internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
@ -506,6 +518,12 @@ namespace Avalonia.Controls
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container));
}
internal void ClearItemContainer(Control container)
{
_scrollViewer?.UnregisterAnchorCandidate(container);
ClearContainerForItemOverride(container);
}
/// <summary>
/// Given a collection of items, adds those that are controls to the logical children.
/// </summary>

1
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -140,6 +140,7 @@ namespace Avalonia.Controls.Presenters
generator.PrepareItemContainer(container, item, index);
itemsControl.AddLogicalChild(container);
children.Insert(index, container);
generator.ItemContainerPrepared(container, item, index);
}
private void ClearItemsControlLogicalChildren()

10
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@ -2,9 +2,11 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@ -264,13 +266,16 @@ namespace Avalonia.Controls
{
Debug.Assert(ItemsControl is not null);
if (items[index] is Control controlItem)
var item = items[index];
if (item is Control controlItem)
{
var generator = ItemsControl!.ItemContainerGenerator;
if (controlItem.IsSet(ItemIsOwnContainerProperty))
{
controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
else if (generator.IsItemItsOwnContainer(controlItem))
@ -278,6 +283,7 @@ namespace Avalonia.Controls
generator.PrepareItemContainer(controlItem, controlItem, index);
AddInternalChild(controlItem);
controlItem.SetValue(ItemIsOwnContainerProperty, true);
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
}
@ -297,6 +303,7 @@ namespace Avalonia.Controls
var recycled = _recyclePool.Pop();
recycled.IsVisible = true;
generator.PrepareItemContainer(recycled, item, index);
generator.ItemContainerPrepared(recycled, item, index);
return recycled;
}
@ -313,6 +320,7 @@ namespace Avalonia.Controls
generator.PrepareItemContainer(container, item, index);
AddInternalChild(container);
generator.ItemContainerPrepared(container, item, index);
return container;
}

12
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -434,20 +434,24 @@ namespace Avalonia.Controls
private Control? GetItemIsOwnContainer(ItemsSourceView items, int index)
{
if (items[index] is Control controlItem)
var item = items[index];
if (item is Control controlItem)
{
var generator = ItemsControl!.ItemContainerGenerator;
if (controlItem.IsSet(ItemIsOwnContainerProperty))
{
controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
else if (generator.IsItemItsOwnContainer(controlItem))
{
AddInternalChild(controlItem);
generator.PrepareItemContainer(controlItem, controlItem, index);
AddInternalChild(controlItem);
controlItem.SetValue(ItemIsOwnContainerProperty, true);
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
}
@ -468,6 +472,7 @@ namespace Avalonia.Controls
var recycled = _recyclePool.Pop();
recycled.IsVisible = true;
generator.PrepareItemContainer(recycled, item, index);
generator.ItemContainerPrepared(recycled, item, index);
return recycled;
}
@ -482,8 +487,9 @@ namespace Avalonia.Controls
var item = items[index];
var container = generator.CreateContainer();
AddInternalChild(container);
generator.PrepareItemContainer(container, item, index);
AddInternalChild(container);
generator.ItemContainerPrepared(container, item, index);
return container;
}

Loading…
Cancel
Save