Skip to content

Commit

Permalink
ScrollView content needs layout when ScrollView.InvalidateMeasure is …
Browse files Browse the repository at this point in the history
…called (#17639)

### Description of Change

When `ScrollView.Content` changes its alignment (like
`HorizontalOptions` change from `Start` to `End`), then the
`ScrollView`'s layout needs to properly update. It worked on Android and
Mac/iOS, but Windows would not update layout until the window was
resized.

After `HorizontalOptions` changed on the content, Windows would call
`InvalidateMeasure` on the WinUI `ScrollView`, but the child content
didn't get updated. This change makes sure that the content also gets
measured.

This fix is helpful for XAML Hot Reload, so the layout will update as
`HorizontalOptions` changes.

### Issues Fixed

Fixes #14377
  • Loading branch information
spadapet authored Nov 27, 2023
2 parents 6d2e068 + 4f6200f commit 34d73c8
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,38 @@ await AttachAndRun(scroll, async (handler) =>
});
}

[Fact]
public async Task TestContentHorizontalOptionsChanged()
{
var label = new Label
{
BackgroundColor = Colors.LightBlue,
HorizontalOptions = LayoutOptions.Start,
Text = "Hello",
WidthRequest = 50,
};

var scrollView = new ScrollView
{
BackgroundColor = Colors.DarkBlue,
Content = label,
WidthRequest = 300,
HeightRequest = 200,
};

SetupBuilder();

await AttachAndRun(scrollView, async (handler) =>
{
// Without this delay, the UI didn't render and the bug didn't repro
await Task.Delay(100);

await WaitAssert(() => CloseEnough(scrollView.Content.Frame.Left, 0.0));
scrollView.Content.HorizontalOptions = LayoutOptions.End;
await WaitAssert(() => CloseEnough(scrollView.Content.Frame.Right, 300.0));
});
}

[Theory]
[InlineData(ScrollOrientation.Vertical, 100, 300, 0, 100)]
[InlineData(ScrollOrientation.Horizontal, 0, 100, 100, 300)]
Expand Down Expand Up @@ -188,22 +220,17 @@ static async Task AssertContentSizeChanged(Task<bool> changed)

static async Task AssertContentSize(Func<Size> actual, Size expected)
{
await WaitAssert(() => CloseEnough(actual(), expected, 0.2), timeout: 5000, message: $"ContentSize was {actual()}, expected {expected}");
await WaitAssert(() => CloseEnough(actual(), expected), timeout: 5000, message: $"ContentSize was {actual()}, expected {expected}");
}

static bool CloseEnough(Size a, Size b, double tolerance)
static bool CloseEnough(double a, double b, double tolerance = 0.2)
{
if (System.Math.Abs(a.Width - b.Width) > tolerance)
{
return false;
}

if (System.Math.Abs(a.Height - b.Height) > tolerance)
{
return false;
}
return System.Math.Abs(a - b) <= tolerance;
}

return true;
static bool CloseEnough(Size a, Size b, double tolerance = 0.2)
{
return CloseEnough(a.Width, b.Width, tolerance) && CloseEnough(a.Height, b.Height, tolerance);
}

static Task<bool> WatchContentSizeChanged(ScrollView scrollView)
Expand Down
10 changes: 10 additions & 0 deletions src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ protected override ScrollViewer CreatePlatformView()
return new ScrollViewer();
}

internal static void MapInvalidateMeasure(IScrollViewHandler handler, IView view, object? args)
{
handler.PlatformView.InvalidateMeasure(view);

if (handler.PlatformView.Content is FrameworkElement content)
{
content.InvalidateMeasure();
}
}

protected override void ConnectHandler(ScrollViewer platformView)
{
base.ConnectHandler(platformView);
Expand Down
5 changes: 4 additions & 1 deletion src/Core/src/Handlers/ScrollView/ScrollViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ public partial class ScrollViewHandler : IScrollViewHandler

public static CommandMapper<IScrollView, IScrollViewHandler> CommandMapper = new(ViewCommandMapper)
{
[nameof(IScrollView.RequestScrollTo)] = MapRequestScrollTo
[nameof(IScrollView.RequestScrollTo)] = MapRequestScrollTo,
#if WINDOWS
[nameof(IView.InvalidateMeasure)] = MapInvalidateMeasure,
#endif
};

public ScrollViewHandler() : base(Mapper, CommandMapper)
Expand Down

0 comments on commit 34d73c8

Please sign in to comment.