dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.26k stars 1.76k forks source link

[Windows] - CarouselView reverts to displaying 1st item in collection when collection modified #25991

Open Mark-NC001 opened 3 days ago

Mark-NC001 commented 3 days ago

Description

I have a CarouselView which uses an ObservableCollection<> as it's ItemSource. The CarouselView appears to behave correctly when scrolling through the collection - but if an item gets added to the collection, the CarouselView reverts to showing the first item in the ItemSource collection.

I would have expected the CarouselView to stay on the item that was displayed (unless of course if it gets removed from the underlying collection in ItemSource, in which case I guess showing the first item is valid?).

Android appears to function correctly.

Steps to Reproduce

Create a data type to store. As an example I'm using a Person class:

namespace MauiTestApp.Classes
{
    public class Person
    {
        public string FirstName { get; set; }
    }
}

And a TestPage:

<?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"
             xmlns:classes="clr-namespace:MauiTestApp.Classes;assembly=MauiTestApp"
                         x:Class="MauiTestApp.TestPage">
    <CarouselView x:Name="TheCarouselView">
        <CarouselView.ItemTemplate>
            <DataTemplate x:DataType="classes:Person">
                <StackLayout>
                    <Label Text="{Binding FirstName}"  FontAttributes="Bold" />
                    <Grid ColumnDefinitions="Auto, Auto, Auto">
                        <Button Grid.Column="0" Text="Back" Clicked="OnBack_Clicked" />
                        <Button Grid.Column="1" Text="Add" Clicked="OnAdd_Clicked" />
                        <Button Grid.Column="2" Text="Next" Clicked="OnNext_Clicked" />
                    </Grid>
                </StackLayout>
            </DataTemplate>
        </CarouselView.ItemTemplate>
    </CarouselView>
</ContentPage>

And the code behind:

using MauiTestApp.Classes;
using System.Collections.ObjectModel;

namespace MauiTestApp;

public partial class TestPage : ContentPage
{

    private ObservableCollection<Person> _people = new ObservableCollection<Person>() { new Person() { FirstName = "Person 1" }, new Person() { FirstName = "Person 2" } };

    public TestPage()
    {
        InitializeComponent();
    }
    protected override void OnAppearing()
    {
        base.OnAppearing();

        TheCarouselView.Loop = false;
        TheCarouselView.ItemsSource = _people;
    }

    private void OnAdd_Clicked(object sender, EventArgs e)
    {
        _people.Add(new Person() { FirstName = "Another Person" });
    }

    private void OnBack_Clicked(object sender, EventArgs e)
    {
        int currentIndex = _people.IndexOf(TheCarouselView.CurrentItem as Person);
        if (currentIndex > 0)
            TheCarouselView.ScrollTo(currentIndex - 1);
    }

    private void OnNext_Clicked(object sender, EventArgs e)
    {
        int currentIndex = _people.IndexOf(TheCarouselView.CurrentItem as Person);
        if (currentIndex < _people.Count - 1)
            TheCarouselView.ScrollTo(currentIndex + 1);
    }
}

When showing the page, initially Person 1 is shown, click Next which will move to Person 2. Then click Add - this adds a person called "Another Person". I would have expected Person 2 to still be displayed, but instead Person 1 gets shown.

Link to public reproduction project repository

https://github.com/Mark-NC001/CarouselViewIssue

Version with bug

8.0.93 SR9.3

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

Windows

Affected platform versions

Windows 10.0.19041.0

Did you find any workaround?

No response

Relevant log output

similar-issues-ai[bot] commented 3 days ago

We've found some similar issues:

If any of the above are duplicates, please consider closing this issue out and adding additional context in the original issue.

Note: You can give me feedback by 👍 or 👎 this comment.

Mark-NC001 commented 2 days ago

Here is a page demonstrating the issue - instructions are included on the page:

using System.Collections.ObjectModel;

namespace MauiTestApp;

public class CarouselViewExample : ContentPage
{

    public class Person
    {
        public string Name { get; set; }
    }

    private ObservableCollection<Person> _collection = new ObservableCollection<Person>() { new Person() { Name = "Person 1" }, new Person() { Name = "Person 2" } };

    private CarouselView MyCarouselView { get; set; }

    public CarouselViewExample()
    {

        StackLayout mainStackLayout = new StackLayout();
        mainStackLayout.Margin = new Thickness(5);

        Label labelInstructions = new Label()
        {
            Text = "Instructions:\nThe CarouselView contains Person 1 and Person 2. It will initially show the first page, Person 1.\n" +
            "Click the 'Scroll to Person 2' button, and the CarouselView will correctly show Person 2.\n" +
            "Click 'Add Person', which will add a new Person record to the collection.\n" +
            "Notice now the CarouselView has jumped back to Person 1!"
        };
        mainStackLayout.Add(labelInstructions);

        MyCarouselView = new CarouselView()
        {
            ItemsSource = _collection,
            Loop = false
        };

        MyCarouselView.ItemTemplate = new DataTemplate(() =>
        {
            StackLayout stackLayout = new StackLayout();
            Label nameLabel = new Label();
            nameLabel.SetBinding(Label.TextProperty, "Name");
            nameLabel.FontSize = 25;
            nameLabel.TextColor = Colors.Red;
            stackLayout.Children.Add(nameLabel);
            return stackLayout;
        });

        mainStackLayout.Add(MyCarouselView);

        Button scrollToPerson2Button = new Button()
        {
            Text = "Scroll to Person 2"
        };
        scrollToPerson2Button.Clicked += ScrollToPerson2Button_Clicked;
        mainStackLayout.Add(scrollToPerson2Button);

        Button addPersonButton = new Button()
        {
            Text = "Add Person",
        };
        addPersonButton.Clicked += AddButton_Clicked;
        mainStackLayout.Add(addPersonButton);

        Content = mainStackLayout;

    }

    private void ScrollToPerson2Button_Clicked(object? sender, EventArgs e)
    {
        MyCarouselView.ScrollTo(1);
    }

    private void AddButton_Clicked(object? sender, EventArgs e)
    {
        _collection.Add(new Person()
        {
            Name = "Person " + (_collection.Count + 1),
        });
    }
}
Mark-NC001 commented 2 days ago

Just as an aside, if you omit setting the Loop property to false, clicking the "Scroll to Person 2" button causes the CarouselView to continuously scroll.... another issue...?! Thankfully I don't want it to loop in my use case.

PureWeen commented 2 days ago

@Mark-NC001 can you upload a project to github that we can reproduce this with?

Mark-NC001 commented 1 day ago

I have added a new repository, found here:

https://github.com/Mark-NC001/CarouselViewIssue

I have never used GitHub repositories before, I am crossing fingers I've done it correctly!