Browse Source

Only prepare items that are containers once.

When an `ItemsControl` returns true for `IsItemItsOwnContainer`, `ItemContainerPrepared` should only be called once the first time the container is prepared.

Requires that `ContainerFromIndex` returns `ItemIsOwnContainer` items that have previously been prepared in order for `SelectingItemsControl` to update their selection correctly when outside the realized viewport.

Fixes #11119
pull/11141/head
Steven Kirk 3 years ago
parent
commit
8a354d8cb9
  1. 28
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  2. 9
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  3. 5
      src/Avalonia.Controls/VirtualizingPanel.cs
  4. 14
      src/Avalonia.Controls/VirtualizingStackPanel.cs

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

@ -7,8 +7,8 @@ namespace Avalonia.Controls.Generators
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// When creating a container for an item from a <see cref="VirtualizingPanel"/>, the following /// When creating a container for an item from a <see cref="VirtualizingPanel"/>, the following
/// method order should be followed: /// process should be followed:
/// ///
/// - <see cref="IsItemItsOwnContainer(Control)"/> should first be called if the item is /// - <see cref="IsItemItsOwnContainer(Control)"/> should first be called if the item is
/// derived from the <see cref="Control"/> class. If this method returns true then the /// derived from the <see cref="Control"/> class. If this method returns true then the
/// item itself should be used as the container. /// item itself should be used as the container.
@ -19,9 +19,29 @@ namespace Avalonia.Controls.Generators
/// - The container should then be added to the panel using /// - The container should then be added to the panel using
/// <see cref="VirtualizingPanel.AddInternalChild(Control)"/> /// <see cref="VirtualizingPanel.AddInternalChild(Control)"/>
/// - Finally, <see cref="ItemContainerPrepared(Control, object?, int)"/> should be called. /// - Finally, <see cref="ItemContainerPrepared(Control, object?, int)"/> should be called.
/// - When the item is ready to be recycled, <see cref="ClearItemContainer(Control)"/> should
/// be called if <see cref="IsItemItsOwnContainer(Control)"/> returned false.
/// ///
/// NOTE: If <see cref="IsItemItsOwnContainer(Control)"/> in the first step above returns true
/// then the above steps should be carried out a single time; the first time the item is
/// displayed. Otherwise the steps should be carried out each time a new container is realized
/// for an item.
///
/// When unrealizing a container, the following process should be followed:
///
/// - If <see cref="IsItemItsOwnContainer(Control)"/> for the item returned true then the item
/// cannot be unrealized or recycled.
/// - Otherwise, <see cref="ClearItemContainer(Control)"/> should be called for the container
/// - If recycling is supported then the container should be added to a recycle pool.
/// - It is assumed that recyclable containers will not be removed from the panel but instead
/// hidden from view using e.g. `container.IsVisible = false`.
///
/// When recycling an unrealized container, the following process should be followed:
///
/// - An element should be taken from the recycle pool.
/// - The container should be made visible.
/// - <see cref="PrepareItemContainer(Control, object?, int)"/> method should be called for the
/// container.
/// - <see cref="ItemContainerPrepared(Control, object?, int)"/> should be called.
///
/// NOTE: Although this class is similar to that found in WPF/UWP, in Avalonia this class only /// NOTE: Although this class is similar to that found in WPF/UWP, in Avalonia this class only
/// concerns itself with generating and clearing item containers; it does not maintain a /// concerns itself with generating and clearing item containers; it does not maintain a
/// record of the currently realized containers, that responsibility is delegated to the /// record of the currently realized containers, that responsibility is delegated to the

9
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@ -168,7 +168,13 @@ namespace Avalonia.Controls
protected internal override Control? ContainerFromIndex(int index) protected internal override Control? ContainerFromIndex(int index)
{ {
return index == _realizedIndex ? _realized : null; if (index < 0 || index >= Items.Count)
return null;
if (index == _realizedIndex)
return _realized;
if (Items[index] is Control c && c.GetValue(ItemIsOwnContainerProperty))
return c;
return null;
} }
protected internal override IEnumerable<Control>? GetRealizedContainers() protected internal override IEnumerable<Control>? GetRealizedContainers()
@ -264,7 +270,6 @@ namespace Avalonia.Controls
if (controlItem.IsSet(ItemIsOwnContainerProperty)) if (controlItem.IsSet(ItemIsOwnContainerProperty))
{ {
controlItem.IsVisible = true; controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem; return controlItem;
} }
else if (generator.IsItemItsOwnContainer(controlItem)) else if (generator.IsItemItsOwnContainer(controlItem))

5
src/Avalonia.Controls/VirtualizingPanel.cs

@ -76,6 +76,11 @@ namespace Avalonia.Controls
/// The container for the item at the specified index within the item collection, if the /// The container for the item at the specified index within the item collection, if the
/// item is realized; otherwise, null. /// item is realized; otherwise, null.
/// </returns> /// </returns>
/// <remarks>
/// Note for implementors: if the item at the 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.
/// </remarks>
protected internal abstract Control? ContainerFromIndex(int index); protected internal abstract Control? ContainerFromIndex(int index);
/// <summary> /// <summary>

14
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Input; using Avalonia.Input;
@ -326,7 +325,17 @@ namespace Avalonia.Controls
return _realizedElements?.Elements.Where(x => x is not null)!; return _realizedElements?.Elements.Where(x => x is not null)!;
} }
protected internal override Control? ContainerFromIndex(int index) => _realizedElements?.GetElement(index); protected internal override Control? ContainerFromIndex(int index)
{
if (index < 0 || index >= Items.Count)
return null;
if (_realizedElements?.GetElement(index) is { } realized)
return realized;
if (Items[index] is Control c && c.GetValue(ItemIsOwnContainerProperty))
return c;
return null;
}
protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1; protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1;
protected internal override Control? ScrollIntoView(int index) protected internal override Control? ScrollIntoView(int index)
@ -578,7 +587,6 @@ namespace Avalonia.Controls
if (controlItem.IsSet(ItemIsOwnContainerProperty)) if (controlItem.IsSet(ItemIsOwnContainerProperty))
{ {
controlItem.IsVisible = true; controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem; return controlItem;
} }
else if (generator.IsItemItsOwnContainer(controlItem)) else if (generator.IsItemItsOwnContainer(controlItem))

Loading…
Cancel
Save