Skip to content

Commit

Permalink
[Windows] Fix crash using ScrollTo on CollectionView (dotnet#19921)
Browse files Browse the repository at this point in the history
* Fix 17865

* Added device test

* Updated test

* Fixup use of event

* Update MauiProgram.cs

* Fix UI test

---------

Co-authored-by: Mike Corsaro <[email protected]>
Co-authored-by: Mike Corsaro <[email protected]>
Co-authored-by: Gerald Versluis <[email protected]>
  • Loading branch information
4 people authored Aug 16, 2024
1 parent 334866f commit 1385a01
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ static async Task<UWPPoint> GetApproximateTargetAsync(ListViewBase list, ScrollV
internal static void JumpToIndexAsync(ListViewBase list, int index, ScrollToPosition scrollToPosition)
{
var scrollViewer = list.GetFirstDescendant<ScrollViewer>();

if (scrollViewer is null)
{
// If ScrollViewer is not found, do nothing.
return;
}

var con = list.ContainerFromIndex(index);
if (con is UIElement uIElement)
{
Expand All @@ -201,8 +208,25 @@ internal static void JumpToIndexAsync(ListViewBase list, int index, ScrollToPosi

public static async Task JumpToItemAsync(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition)
{
if(!list.IsLoaded)
{
list.OnLoaded(async () =>
{
// If the ListView is not loaded, wait for it to load and reinvoke the JumpToItem.
await JumpToItemAsync(list, targetItem, scrollToPosition);
});

return;
}

var scrollViewer = list.GetFirstDescendant<ScrollViewer>();

if (scrollViewer is null)
{
// If ScrollViewer is not found, do nothing.
return;
}

var tcs = new TaskCompletionSource<object>();
Func<Task> adjust = null;

Expand Down Expand Up @@ -271,6 +295,17 @@ static async Task<bool> ScrollToItemAsync(ListViewBase list, object targetItem,

public static async Task AnimateToItemAsync(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition)
{
if (!list.IsLoaded)
{
list.OnLoaded(async () =>
{
// If the ListView is not loaded, wait for it to load and reinvoke the AnimateToItem.
await AnimateToItemAsync(list, targetItem, scrollToPosition);
});

return;
}

var scrollViewer = list.GetFirstDescendant<ScrollViewer>();

if (scrollViewer == null)
Expand Down
37 changes: 37 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17865.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue17865"
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues"
Title="Issue 17865">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="*"/>
</Grid.RowDefinitions>
<HorizontalStackLayout
Padding="5">
<Button
AutomationId="WaitForStubControl"
Text="Reveal Last Item"
Clicked="OnButtonClicked"/>
</HorizontalStackLayout>
<CollectionView
x:Name="collectionView"
Grid.Row="1"
ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="5,2"
Text="{Binding ItemText}"
HorizontalOptions="Fill"
TextColor="Gray"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

</ContentPage>
71 changes: 71 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17865.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues
{
internal class Issue17865Model
{
public Issue17865Model(int itemIndex)
{
ItemText = $"Item #{itemIndex}";
}

public string ItemText { get; }

}

internal class Issue17865ViewModel : BindableObject
{
ObservableCollection<Issue17865Model> items = new();

public Issue17865ViewModel()
{
Populate();
}

void Populate()
{
for (int i = 0; i < 100; i++)
{
items.Add(new Issue17865Model(i));
}
}

public ObservableCollection<Issue17865Model> Items => items;
}

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 17865, "CollectionView throws NRE when ScrollTo method is called from a handler of event Window.Created", PlatformAffected.UWP)]
public partial class Issue17865 : ContentPage
{
readonly Issue17865ViewModel _viewModel;

public Issue17865()
{
InitializeComponent();

collectionView.Loaded += CollectionView_Loaded;

BindingContext = _viewModel = new Issue17865ViewModel();
}

private void CollectionView_Loaded(object sender, EventArgs e)
{
RevealLastItem();
}

public void RevealLastItem()
{
var item = _viewModel.Items.Last();
collectionView.ScrollTo(item, null, ScrollToPosition.MakeVisible, false);
}

private void OnButtonClicked(object sender, EventArgs e)
{
RevealLastItem();
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/tests/TestCases.HostApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ protected override Window CreateWindow(IActivationState activationState)
}
#endif
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if WINDOWS
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue17865 : _IssuesUITest
{
const string ButtonId = "WaitForStubControl";

public Issue17865(TestDevice device) : base(device) { }

public override string Issue => "CollectionView throws NRE when ScrollTo method is called from a handler of event Window.Created";

[Test]
[Category(UITestCategories.CollectionView)]
public void Issue17865Test()
{
App.WaitForElement(ButtonId);

App.Click(ButtonId);

// NOTE: Without crashes the test has passed.
}
}
}
#endif

0 comments on commit 1385a01

Please sign in to comment.