Browse Source

Added PanelChildren.

After this change the `Panel.Children` collection is also used as the logical/visual child store, preventing the need to store the children of a panel 3 times.
feature/ilogicalvisualchildren
Steven Kirk 4 years ago
parent
commit
2bbef79872
  1. 6
      src/Avalonia.Base/Controls/LogicalVisualChildren.cs
  2. 7
      src/Avalonia.Controls/Grid.cs
  3. 64
      src/Avalonia.Controls/Panel.cs
  4. 191
      src/Avalonia.Controls/PanelChildren.cs
  5. 2
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  6. 11
      src/Avalonia.Controls/VirtualizingStackPanel.cs

6
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)

7
src/Avalonia.Controls/Grid.cs

@ -570,13 +570,10 @@ namespace Avalonia.Controls
return (arrangeSize);
}
/// <summary>
/// <see cref="Panel.ChildrenChanged"/>
/// </summary>
protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
protected internal override void InvalidateOnChildrenChanged()
{
CellsStructureDirty = true;
base.ChildrenChanged(sender, e);
base.InvalidateOnChildrenChanged();
}
/// <summary>

64
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
/// </summary>
public Panel()
{
Children.CollectionChanged += ChildrenChanged;
}
/// <summary>
/// Gets the children of the <see cref="Panel"/>.
/// </summary>
[Content]
public new Controls Children { get; } = new Controls();
public new Controls Children => (Controls)base.Children;
/// <summary>
/// Gets or Sets Panel background brush.
@ -81,6 +74,8 @@ namespace Avalonia.Controls
base.Render(context);
}
protected override ILogicalVisualChildren CreateChildren() => new PanelChildren(this);
/// <summary>
/// Marks a property on a child as affecting the parent panel's arrangement.
/// </summary>
@ -108,56 +103,19 @@ namespace Avalonia.Controls
}
/// <summary>
/// Called when the <see cref="Children"/> collection changes.
/// Called in response to the <see cref="Children"/> collection changing in order to allow
/// the panel to carry out any needed invalidation.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
protected internal virtual void InvalidateOnChildrenChanged()
{
List<Control> controls;
var logicalChildren = (IAvaloniaList<ILogical>)LogicalChildren;
var visualChildren = (IAvaloniaList<IVisual>)VisualChildren;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
controls = e.NewItems.OfType<Control>().ToList();
logicalChildren.InsertRange(e.NewStartingIndex, controls);
visualChildren.InsertRange(e.NewStartingIndex, e.NewItems.OfType<Visual>());
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<Control>().ToList();
logicalChildren.RemoveAll(controls);
visualChildren.RemoveAll(e.OldItems.OfType<Visual>());
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<TPanel>(AvaloniaPropertyChangedEventArgs e)

191
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
{
/// <summary>
/// A <see cref="Controls"/> collection held by a <see cref="Panel"/>.
/// </summary>
internal class PanelChildren : Controls,
ILogicalVisualChildren,
IList<ILogical>,
IList<IVisual>
{
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<IControl> 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<ILogical>.this[int index]
{
get => this[index];
set => this[index] = (IControl)value;
}
IVisual IList<IVisual>.this[int index]
{
get => this[index];
set => this[index] = (IControl)value;
}
IReadOnlyList<ILogical> ILogicalVisualChildren.Logical => this;
IReadOnlyList<IVisual> ILogicalVisualChildren.Visual => this;
IList<ILogical> ILogicalVisualChildren.LogicalMutable => this;
IList<IVisual> ILogicalVisualChildren.VisualMutable => this;
bool ICollection<ILogical>.IsReadOnly => true;
bool ICollection<IVisual>.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<ILogical>.Add(ILogical item) => Add((IControl)item);
void ICollection<IVisual>.Add(IVisual item) => Add((IControl)item);
bool ICollection<ILogical>.Contains(ILogical item) => item is IControl c ? Contains(c) : false;
bool ICollection<IVisual>.Contains(IVisual item) => item is IControl c ? Contains(c) : false;
void ICollection<ILogical>.CopyTo(ILogical[] array, int arrayIndex) => ((ICollection)this).CopyTo(array, arrayIndex);
void ICollection<IVisual>.CopyTo(IVisual[] array, int arrayIndex) => ((ICollection)this).CopyTo(array, arrayIndex);
IEnumerator<ILogical> IEnumerable<ILogical>.GetEnumerator() => GetEnumerator();
IEnumerator<IVisual> IEnumerable<IVisual>.GetEnumerator() => GetEnumerator();
int IList<ILogical>.IndexOf(ILogical item) => item is IControl c ? IndexOf(c) : -1;
int IList<IVisual>.IndexOf(IVisual item) => item is IControl c ? IndexOf(c) : -1;
void IList<ILogical>.Insert(int index, ILogical item) => Insert(index, (IControl)item);
void IList<IVisual>.Insert(int index, IVisual item) => Insert(index, (IControl)item);
bool ICollection<ILogical>.Remove(ILogical item) => Remove((IControl)item);
bool ICollection<IVisual>.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);
}
}
}

2
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.
}

11
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:

Loading…
Cancel
Save