diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs
index d62acc0d52..dd99c40cd3 100644
--- a/src/Avalonia.Base/Animation/Animation.cs
+++ b/src/Avalonia.Base/Animation/Animation.cs
@@ -200,7 +200,7 @@ namespace Avalonia.Animation
///
/// The animation setter.
/// The property animator value.
- public static void SetAnimator(IAnimationSetter setter,
+ public static void SetAnimator(IAnimationSetter setter,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
Type value)
{
@@ -319,7 +319,7 @@ namespace Avalonia.Animation
if (animators.Count == 1)
{
var subscription = animators[0].Apply(this, control, clock, match, onComplete);
-
+
if (subscription is not null)
{
subscriptions.Add(subscription);
@@ -348,9 +348,11 @@ namespace Avalonia.Animation
if (onComplete != null)
{
- Task.WhenAll(completionTasks!).ContinueWith(
- (_, state) => ((Action)state!).Invoke(),
- onComplete);
+ Task.WhenAll(completionTasks!)
+ .ContinueWith((_, state) => ((Action)state!).Invoke()
+ , onComplete
+ , TaskScheduler.FromCurrentSynchronizationContext()
+ );
}
}
return new CompositeDisposable(subscriptions);
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 747ee1c082..f47738f2e4 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -249,10 +249,12 @@ namespace Avalonia.Layout
{
var control = _toMeasure.Dequeue();
- if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
+ if (!control.IsMeasureValid)
{
Measure(control);
}
+
+ _toArrange.Enqueue(control);
}
}
@@ -262,7 +264,7 @@ namespace Avalonia.Layout
{
var control = _toArrange.Dequeue();
- if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
+ if (!control.IsArrangeValid)
{
Arrange(control);
}
@@ -297,8 +299,6 @@ namespace Avalonia.Layout
{
control.Measure(control.PreviousMeasure.Value);
}
-
- _toArrange.Enqueue(control);
}
return true;
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 329a0fa6ab..736c338c10 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -1,5 +1,5 @@
using System;
-
+using Avalonia.Collections;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
@@ -442,7 +442,7 @@ namespace Avalonia.Controls.Presenters
var contentTemplate = ContentTemplate;
var oldChild = Child;
var newChild = CreateChild(content, oldChild, contentTemplate);
- var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
+ var logicalChildren = GetEffectiveLogicalChildren();
// Remove the old child if we're not recycling it.
if (newChild != oldChild)
@@ -488,6 +488,9 @@ namespace Avalonia.Controls.Presenters
}
+ private IAvaloniaList GetEffectiveLogicalChildren()
+ => Host?.LogicalChildren ?? LogicalChildren;
+
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
@@ -692,7 +695,7 @@ namespace Avalonia.Controls.Presenters
else if (Child != null)
{
VisualChildren.Remove(Child);
- LogicalChildren.Remove(Child);
+ GetEffectiveLogicalChildren().Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
_recyclingDataTemplate = null;
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
index 09f78c5a6c..45a6efdd4a 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
@@ -514,5 +514,38 @@ namespace Avalonia.Base.UnitTests.Layout
Assert.True(parent.IsMeasureValid);
Assert.True(parent.IsArrangeValid);
}
+
+ [Fact]
+ public void Grandparent_Can_Invalidate_Root_Measure_During_Arrange()
+ {
+ // Issue #11161.
+ var child = new LayoutTestControl();
+ var parent = new LayoutTestControl { Child = child };
+ var grandparent = new LayoutTestControl { Child = parent };
+ var root = new LayoutTestRoot { Child = grandparent };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ grandparent.DoArrangeOverride = (_, s) =>
+ {
+ root.InvalidateMeasure();
+ return s;
+ };
+ grandparent.CallBaseArrange = true;
+
+ child.InvalidateMeasure();
+ grandparent.InvalidateMeasure();
+
+ root.LayoutManager.ExecuteLayoutPass();
+
+ Assert.True(child.IsMeasureValid);
+ Assert.True(child.IsArrangeValid);
+ Assert.True(parent.IsMeasureValid);
+ Assert.True(parent.IsArrangeValid);
+ Assert.True(grandparent.IsMeasureValid);
+ Assert.True(grandparent.IsArrangeValid);
+ Assert.True(root.IsMeasureValid);
+ Assert.True(root.IsArrangeValid);
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs
index 62de81006e..d85c7ed9bc 100644
--- a/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs
+++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs
@@ -10,21 +10,41 @@ namespace Avalonia.Base.UnitTests.Layout
public bool Arranged { get; set; }
public Func DoMeasureOverride { get; set; }
public Func DoArrangeOverride { get; set; }
+ public bool CallBaseMeasure { get; set; }
+ public bool CallBaseArrange { get; set; }
protected override Size MeasureOverride(Size availableSize)
{
Measured = true;
- return DoMeasureOverride != null ?
- DoMeasureOverride(this, availableSize) :
- base.MeasureOverride(availableSize);
+
+ if (DoMeasureOverride is not null)
+ {
+ var overrideResult = DoMeasureOverride(this, availableSize);
+ return CallBaseMeasure ?
+ base.MeasureOverride(overrideResult) :
+ overrideResult;
+ }
+ else
+ {
+ return base.MeasureOverride(availableSize);
+ }
}
protected override Size ArrangeOverride(Size finalSize)
{
Arranged = true;
- return DoArrangeOverride != null ?
- DoArrangeOverride(this, finalSize) :
- base.ArrangeOverride(finalSize);
+
+ if (DoArrangeOverride is not null)
+ {
+ var overrideResult = DoArrangeOverride(this, finalSize);
+ return CallBaseArrange ?
+ base.ArrangeOverride(overrideResult) :
+ overrideResult;
+ }
+ else
+ {
+ return base.ArrangeOverride(finalSize);
+ }
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
index bece711426..d5e4693666 100644
--- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
@@ -359,16 +359,21 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate();
Assert.Equal(target, target.Presenter.Child.GetLogicalParent());
+ Assert.Equal(new[] { target.Presenter.Child }, target.LogicalChildren);
root.Child = null;
Assert.Null(target.Template);
target.Content = null;
+
+ Assert.Empty(target.LogicalChildren);
+
root.Child = target;
target.Content = "Bar";
Assert.Equal(target, target.Presenter.Child.GetLogicalParent());
+ Assert.Equal(new[] { target.Presenter.Child }, target.LogicalChildren);
}
private static FuncControlTemplate GetTemplate()