Skip to content

Commit

Permalink
Fix LayoutUpdated and EffectiveViewportChanged double registration (#…
Browse files Browse the repository at this point in the history
…17196)

* Add failing test for LayoutUpdated and EffectiveViewportChanged

* Fix LayoutUpdated and EffectiveViewportChanged registration
  • Loading branch information
MrJul authored Oct 7, 2024
1 parent 20db30d commit 51dbf95
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 3 deletions.
16 changes: 13 additions & 3 deletions src/Avalonia.Base/Layout/Layoutable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public class Layoutable : Visual
private Rect? _previousArrange;
private EventHandler<EffectiveViewportChangedEventArgs>? _effectiveViewportChanged;
private EventHandler? _layoutUpdated;
private bool _isAttachingToVisualTree;

/// <summary>
/// Initializes static members of the <see cref="Layoutable"/> class.
Expand Down Expand Up @@ -164,7 +165,7 @@ public event EventHandler<EffectiveViewportChangedEventArgs>? EffectiveViewportC
{
add
{
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
Expand All @@ -190,7 +191,7 @@ public event EventHandler? LayoutUpdated
{
add
{
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
Expand Down Expand Up @@ -735,9 +736,18 @@ internal sealed override void InvalidateStyles(bool recurse)
InvalidateMeasure();
}

/// <inheritdoc />
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
_isAttachingToVisualTree = true;
try
{
base.OnAttachedToVisualTreeCore(e);
}
finally
{
_isAttachingToVisualTree = false;
}

if (e.Root is ILayoutRoot r)
{
Expand Down
24 changes: 24 additions & 0 deletions tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,30 @@ public void LayoutManager_LayoutUpdated_Is_Unsubscribed_When_Detached_From_Tree(
Times.Once);
}

[Fact]
public void LayoutManager_LayoutUpdated_Should_Not_Be_Subscribed_Twice_In_AttachedToVisualTree()
{
Border border1;
var layoutManager = new Mock<ILayoutManager>();
layoutManager.SetupAdd(m => m.LayoutUpdated += (_, _) => { });

_ = new TestRoot
{
Child = border1 = new Border(),
LayoutManager = layoutManager.Object,
};

var border2 = new Border();
border2.AttachedToVisualTree += (_, _) => border2.LayoutUpdated += (_, _) => { };

layoutManager.Invocations.Clear();
border1.Child = border2;

layoutManager.VerifyAdd(
x => x.LayoutUpdated += It.IsAny<EventHandler>(),
Times.Once);
}

[Fact]
public void Making_Control_Invisible_Should_Invalidate_Parent_Measure()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ await RunOnUIThread.Execute(async () =>
});
}

[Fact]
public async Task EffectiveViewportChanged_Should_Not_Be_Raised_Twice_If_Subcribed_In_AttachedToVisualTree()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas();
var raised = 0;
target.AttachedToVisualTree += (_, _) =>
{
target.EffectiveViewportChanged += (_, _) => ++raised;
};
root.Child = target;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
});
}

[Fact]
public async Task Parent_Affects_EffectiveViewport()
{
Expand Down

0 comments on commit 51dbf95

Please sign in to comment.