ScrollView content needs layout when ScrollView.InvalidateMeasure is 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
This commit is contained in:
Peter Spada 2023-11-27 13:50:02 -08:00 коммит произвёл GitHub
Родитель 6d2e068493 4f6200ff68
Коммит 34d73c8ee5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 53 добавлений и 13 удалений

Просмотреть файл

@ -60,6 +60,38 @@ namespace Microsoft.Maui.DeviceTests
});
}
[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)]
@ -188,22 +220,17 @@ namespace Microsoft.Maui.DeviceTests
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;
}
return System.Math.Abs(a - b) <= tolerance;
}
if (System.Math.Abs(a.Height - b.Height) > tolerance)
{
return false;
}
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)

Просмотреть файл

@ -20,6 +20,16 @@ namespace Microsoft.Maui.Handlers
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);

Просмотреть файл

@ -29,7 +29,10 @@ namespace Microsoft.Maui.Handlers
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)