diff --git a/src/Avalonia.Base/Controls/LogicalVisualChildren.cs b/src/Avalonia.Base/Controls/LogicalVisualChildren.cs
index 933b1971e4..bcbd21adbe 100644
--- a/src/Avalonia.Base/Controls/LogicalVisualChildren.cs
+++ b/src/Avalonia.Base/Controls/LogicalVisualChildren.cs
@@ -119,12 +119,6 @@ namespace Avalonia.Controls
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection");
}
-
- // HACK: Remove this.
- if (_owner is IVisual v)
- {
- v?.GetVisualRoot()?.Renderer?.RecalculateChildren(v);
- }
}
private void OnVisualCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index 564cc93a39..268e0115ef 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -570,13 +570,10 @@ namespace Avalonia.Controls
return (arrangeSize);
}
- ///
- ///
- ///
- protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+ protected internal override void InvalidateOnChildrenChanged()
{
CellsStructureDirty = true;
- base.ChildrenChanged(sender, e);
+ base.InvalidateOnChildrenChanged();
}
///
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index 58a45cdceb..dfc87a64ff 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -1,13 +1,7 @@
using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Linq;
-using Avalonia.Collections;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
-using Avalonia.Styling;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -41,14 +35,13 @@ namespace Avalonia.Controls
///
public Panel()
{
- Children.CollectionChanged += ChildrenChanged;
}
///
/// Gets the children of the .
///
[Content]
- public new Controls Children { get; } = new Controls();
+ public new Controls Children => (Controls)base.Children;
///
/// Gets or Sets Panel background brush.
@@ -81,6 +74,8 @@ namespace Avalonia.Controls
base.Render(context);
}
+ protected override ILogicalVisualChildren CreateChildren() => new PanelChildren(this);
+
///
/// Marks a property on a child as affecting the parent panel's arrangement.
///
@@ -108,56 +103,19 @@ namespace Avalonia.Controls
}
///
- /// Called when the collection changes.
+ /// Called in response to the collection changing in order to allow
+ /// the panel to carry out any needed invalidation.
///
- /// The event sender.
- /// The event args.
- protected virtual void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+ protected internal virtual void InvalidateOnChildrenChanged()
{
- List controls;
- var logicalChildren = (IAvaloniaList)LogicalChildren;
- var visualChildren = (IAvaloniaList)VisualChildren;
-
- switch (e.Action)
- {
- case NotifyCollectionChangedAction.Add:
- controls = e.NewItems.OfType().ToList();
- logicalChildren.InsertRange(e.NewStartingIndex, controls);
- visualChildren.InsertRange(e.NewStartingIndex, e.NewItems.OfType());
- break;
-
- case NotifyCollectionChangedAction.Move:
- logicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
- visualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
- break;
-
- case NotifyCollectionChangedAction.Remove:
- controls = e.OldItems.OfType().ToList();
- logicalChildren.RemoveAll(controls);
- visualChildren.RemoveAll(e.OldItems.OfType());
- break;
-
- case NotifyCollectionChangedAction.Replace:
- for (var i = 0; i < e.OldItems.Count; ++i)
- {
- var index = i + e.OldStartingIndex;
- var child = (IControl)e.NewItems[i];
- LogicalChildren[index] = child;
- VisualChildren[index] = child;
- }
- break;
-
- case NotifyCollectionChangedAction.Reset:
- throw new NotSupportedException();
- }
-
- _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
- InvalidateMeasureOnChildrenChanged();
+ OnChildIndexChanged(new ChildIndexChangedEventArgs());
+ InvalidateMeasure();
+ VisualRoot?.Renderer?.RecalculateChildren(this);
}
- private protected virtual void InvalidateMeasureOnChildrenChanged()
+ protected void OnChildIndexChanged(ChildIndexChangedEventArgs e)
{
- InvalidateMeasure();
+ _childIndexChanged?.Invoke(this, e);
}
private static void AffectsParentArrangeInvalidate(AvaloniaPropertyChangedEventArgs e)
diff --git a/src/Avalonia.Controls/PanelChildren.cs b/src/Avalonia.Controls/PanelChildren.cs
new file mode 100644
index 0000000000..0672660562
--- /dev/null
+++ b/src/Avalonia.Controls/PanelChildren.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.LogicalTree;
+using Avalonia.VisualTree;
+
+#nullable enable
+
+namespace Avalonia.Controls
+{
+ ///
+ /// A collection held by a .
+ ///
+ internal class PanelChildren : Controls,
+ ILogicalVisualChildren,
+ IList,
+ IList
+ {
+ private readonly Panel _owner;
+
+ public PanelChildren(Panel owner)
+ {
+ _owner = owner;
+ Validate = ValidateItem;
+ }
+
+ public override IControl this[int index]
+ {
+ get => base[index];
+ set
+ {
+ var oldValue = base[index];
+
+ if (oldValue != value)
+ {
+ ClearParent(oldValue);
+ base[index] = value;
+ }
+ }
+ }
+
+ public override void Add(IControl item)
+ {
+ base.Add(item);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override void Insert(int index, IControl item)
+ {
+ base.Insert(index, item);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override void InsertRange(int index, IEnumerable items)
+ {
+ base.InsertRange(index, items);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override bool Remove(IControl item)
+ {
+ var result = base.Remove(item);
+
+ if (result)
+ {
+ ClearParent(item);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ return result;
+ }
+
+ public override void RemoveAt(int index)
+ {
+ ClearParent(this[index]);
+ base.RemoveAt(index);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override void RemoveRange(int index, int count)
+ {
+ if (count > 0)
+ {
+ base.RemoveRange(index, count);
+ _owner.InvalidateOnChildrenChanged();
+ }
+ }
+
+ public override void Move(int oldIndex, int newIndex)
+ {
+ base.Move(oldIndex, newIndex);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override void MoveRange(int oldIndex, int count, int newIndex)
+ {
+ base.MoveRange(oldIndex, count, newIndex);
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ public override void Clear()
+ {
+ foreach (var item in this)
+ {
+ if (item.LogicalParent == _owner)
+ ((ISetLogicalParent)item).SetParent(null);
+ ((ISetVisualParent)item).SetParent(null);
+ }
+
+ base.Clear();
+ _owner.InvalidateOnChildrenChanged();
+ }
+
+ ILogical IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = (IControl)value;
+ }
+
+ IVisual IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = (IControl)value;
+ }
+
+ IReadOnlyList ILogicalVisualChildren.Logical => this;
+ IReadOnlyList ILogicalVisualChildren.Visual => this;
+ IList ILogicalVisualChildren.LogicalMutable => this;
+ IList ILogicalVisualChildren.VisualMutable => this;
+ bool ICollection.IsReadOnly => true;
+ bool ICollection.IsReadOnly => true;
+
+ void ILogicalVisualChildren.AddLogicalChildrenChangedHandler(NotifyCollectionChangedEventHandler handler)
+ {
+ CollectionChanged += handler;
+ }
+
+ void ILogicalVisualChildren.RemoveLogicalChildrenChangedHandler(NotifyCollectionChangedEventHandler handler)
+ {
+ CollectionChanged -= handler;
+ }
+
+ void ILogicalVisualChildren.AddVisualChildrenChangedHandler(NotifyCollectionChangedEventHandler handler)
+ {
+ CollectionChanged += handler;
+ }
+
+ void ILogicalVisualChildren.RemoveVisualChildrenChangedHandler(NotifyCollectionChangedEventHandler handler)
+ {
+ CollectionChanged -= handler;
+ }
+
+ void ICollection.Add(ILogical item) => Add((IControl)item);
+ void ICollection.Add(IVisual item) => Add((IControl)item);
+ bool ICollection.Contains(ILogical item) => item is IControl c ? Contains(c) : false;
+ bool ICollection.Contains(IVisual item) => item is IControl c ? Contains(c) : false;
+ void ICollection.CopyTo(ILogical[] array, int arrayIndex) => ((ICollection)this).CopyTo(array, arrayIndex);
+ void ICollection.CopyTo(IVisual[] array, int arrayIndex) => ((ICollection)this).CopyTo(array, arrayIndex);
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ int IList.IndexOf(ILogical item) => item is IControl c ? IndexOf(c) : -1;
+ int IList.IndexOf(IVisual item) => item is IControl c ? IndexOf(c) : -1;
+ void IList.Insert(int index, ILogical item) => Insert(index, (IControl)item);
+ void IList.Insert(int index, IVisual item) => Insert(index, (IControl)item);
+ bool ICollection.Remove(ILogical item) => Remove((IControl)item);
+ bool ICollection.Remove(IVisual item) => Remove((IControl)item);
+
+ private void ClearParent(IControl? c)
+ {
+ if (c is null)
+ return;
+ if (c.LogicalParent == _owner)
+ ((ISetLogicalParent)c).SetParent(null);
+ ((ISetVisualParent)c).SetParent(null);
+ }
+
+ private void ValidateItem(IControl c)
+ {
+ _ = c ?? throw new ArgumentException("Cannot add null to Panel.Children.");
+ if (c.VisualParent is not null)
+ throw new InvalidOperationException("The control already has a visual parent.");
+
+ // It's a bit naughty to do this during validation, but saves iterating the
+ // added items multiple times in the case of an AddRange.
+ if (c.LogicalParent is null)
+ ((ISetLogicalParent)c).SetParent(_owner);
+ ((ISetVisualParent)c).SetParent(_owner);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index ecc0fa3a48..edcf4eab13 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -288,7 +288,7 @@ namespace Avalonia.Controls
return result;
}
- private protected override void InvalidateMeasureOnChildrenChanged()
+ protected internal override void InvalidateOnChildrenChanged()
{
// Don't invalidate measure when children change.
}
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index da8bbe4fcf..4602dca73c 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -78,6 +78,13 @@ namespace Avalonia.Controls
_forceRemeasure = true;
}
+ protected override ILogicalVisualChildren CreateChildren()
+ {
+ var result = base.CreateChildren();
+ result.AddLogicalChildrenChangedHandler(ChildrenChanged);
+ return result;
+ }
+
protected override Size MeasureOverride(Size availableSize)
{
if (_forceRemeasure || availableSize != ((ILayoutable)this).PreviousMeasure)
@@ -103,10 +110,8 @@ namespace Avalonia.Controls
return result;
}
- protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+ private void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
{
- base.ChildrenChanged(sender, e);
-
switch (e.Action)
{
case NotifyCollectionChangedAction.Add: