Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public partial class CarouselViewHandler : ItemsViewHandler<CarouselView>
bool _isCarouselViewReady;
NotifyCollectionChangedEventHandler _collectionChanged;
readonly WeakNotifyCollectionChangedProxy _proxy = new();
int _lastScrolledToPosition = -1; // tracks last position we issued ScrollTo for, to avoid re-entry

~CarouselViewHandler() => _proxy.Unsubscribe();

Expand Down Expand Up @@ -360,14 +361,9 @@ void UpdateInitialPosition()

if (ListViewBase.Items.Count > 0)
{
if (Element.Loop)
{
var item = ItemsView.CurrentItem ?? ListViewBase.Items.FirstOrDefault();
_loopableCollectionView.CenterMode = true;
ListViewBase.ScrollIntoView(item);
_loopableCollectionView.CenterMode = false;
}

// Loop centering is no longer needed here: UpdateCurrentItem and UpdatePosition
// now use _lastScrolledToPosition to guard against re-entry and scroll to the
// correct centered position programmatically, making the explicit CenterMode block redundant.
if (ItemsView.CurrentItem != null)
UpdateCurrentItem();
else
Expand All @@ -387,7 +383,11 @@ void UpdateCurrentItem()
if (currentItemPosition < 0 || currentItemPosition >= ItemCount)
return;

ItemsView.ScrollTo(currentItemPosition, position: ScrollToPosition.Center, animate: ItemsView.AnimateCurrentItemChanges);
if (ItemsView.Position != currentItemPosition && _lastScrolledToPosition != currentItemPosition)
{
_lastScrolledToPosition = currentItemPosition;
ItemsView.ScrollTo(currentItemPosition, position: ScrollToPosition.Center, animate: ItemsView.AnimateCurrentItemChanges);
}
}

void UpdatePosition()
Expand All @@ -400,6 +400,12 @@ void UpdatePosition()
if (carouselPosition < 0 || carouselPosition >= ItemCount)
return;

if (!ItemsView.IsDragging && !ItemsView.IsScrolling && carouselPosition != _lastScrolledToPosition)
{
_lastScrolledToPosition = carouselPosition;
ItemsView.ScrollTo(carouselPosition, position: ScrollToPosition.Center, animate: ItemsView.AnimateCurrentItemChanges);
}

SetCarouselViewCurrentItem(carouselPosition);
}

Expand Down Expand Up @@ -498,6 +504,12 @@ void CarouselScrolled(object sender, ItemsViewScrolledEventArgs e)
return;
}

// User scrolled to this position — reset tracker so a future programmatic scroll to same index still fires
if (position != _lastScrolledToPosition)
{
_lastScrolledToPosition = -1;
}

if (position == Element.Position)
{
return;
Expand All @@ -523,6 +535,7 @@ void OnScrollViewChanged(object sender, ScrollViewerViewChangedEventArgs e)

void OnCollectionItemsSourceChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_lastScrolledToPosition = -1;
var carouselPosition = ItemsView.Position;
var currentItemPosition = GetItemPositionInCarousel(ItemsView.CurrentItem);
var count = (sender as IList).Count;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
149 changes: 149 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue27563.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Collections.ObjectModel;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 27563, "[Windows] CarouselView Scrolling Issue", PlatformAffected.UWP)]
public partial class Issue27563 : ContentPage
{
public Issue27563()
{
var verticalStackLayout = new VerticalStackLayout();

var carouselItems = new ObservableCollection<string>
{
"Remain View",
"Actual View",
"Percentage View",
};

CarouselView carousel = new CarouselView
{
ItemsSource = carouselItems,
AutomationId = "carouselview",
HeightRequest = 200,
ItemTemplate = new DataTemplate(() =>
{
var grid = new Grid
{
Padding = 10
};

var label = new Label
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
FontSize = 18,
};
label.SetBinding(Label.TextProperty, ".");
label.SetBinding(Label.AutomationIdProperty, ".");

grid.Children.Add(label);
return grid;
}),
HorizontalOptions = LayoutOptions.Fill,
};

var indicatorView = new IndicatorView
{
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};

carousel.IndicatorView = indicatorView;

var carouselPositionLabel = new Label
{
Text = "CarouselPos:0",
AutomationId = "carouselPositionLabel",
HorizontalOptions = LayoutOptions.Center,
};

var indicatorPositionLabel = new Label
{
Text = "IndicatorPos:0",
AutomationId = "indicatorPositionLabel",
HorizontalOptions = LayoutOptions.Center,
};

var pingLabel = new Label
{
Text = "Ping:0",
AutomationId = "pingLabel",
HorizontalOptions = LayoutOptions.Center,
};

var currentItemLabel = new Label
{
Text = "CurrentItem:Remain View",
AutomationId = "currentItemLabel",
HorizontalOptions = LayoutOptions.Center,
};

var scrollToSecondButton = new Button
{
Text = "Scroll To Second Item",
AutomationId = "ScrollToSecondButton",
Margin = new Thickness(20, 10),
};

scrollToSecondButton.Clicked += (sender, e) =>
{
carousel.ScrollTo(1, position: ScrollToPosition.Center, animate: false);
};

var positionButton = new Button
{
Text = "Change IndicatorView Position",
AutomationId = "PositionButton",
Margin = new Thickness(20, 10),
};

positionButton.Clicked += (sender, e) =>
{
indicatorView.Position = 2;
};

var pingCount = 0;
var pingButton = new Button
{
Text = "Ping",
AutomationId = "PingButton",
Margin = new Thickness(20, 10),
};

pingButton.Clicked += (sender, e) =>
{
pingCount++;
pingLabel.Text = $"Ping:{pingCount}";
};

carousel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == CarouselView.PositionProperty.PropertyName)
carouselPositionLabel.Text = $"CarouselPos:{carousel.Position}";
};

indicatorView.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == IndicatorView.PositionProperty.PropertyName)
indicatorPositionLabel.Text = $"IndicatorPos:{indicatorView.Position}";
};

carousel.CurrentItemChanged += (sender, e) =>
{
currentItemLabel.Text = $"CurrentItem:{carousel.CurrentItem}";
};

verticalStackLayout.Children.Add(carousel);
verticalStackLayout.Children.Add(indicatorView);
verticalStackLayout.Children.Add(scrollToSecondButton);
verticalStackLayout.Children.Add(positionButton);
verticalStackLayout.Children.Add(pingButton);
verticalStackLayout.Children.Add(carouselPositionLabel);
verticalStackLayout.Children.Add(indicatorPositionLabel);
verticalStackLayout.Children.Add(currentItemLabel);
verticalStackLayout.Children.Add(pingLabel);

Content = verticalStackLayout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue27563 : _IssuesUITest
{
public override string Issue => "[Windows] CarouselView Scrolling Issue";

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

#if !WINDOWS // On Windows, TimeoutException is thrown when enabling the Loop. Refer issue: https://github.com/dotnet/maui/issues/29245
[Test, Order(1)]
[Category(UITestCategories.CarouselView)]
public void VerifyCarouselViewIndicatorPositionWithoutLooping()
{
App.WaitForElement("carouselview");
App.WaitForElement("Remain View");

App.Tap("ScrollToSecondButton");
App.WaitForElement("Actual View");

App.Tap("PositionButton");
App.WaitForElement("Percentage View");

App.Tap("PingButton");
App.WaitForElement("Ping:1");
App.Tap("ScrollToSecondButton");
}
#endif

#if !WINDOWS && !MACCATALYST
// On Catalyst, Swipe actions not supported in Appium.
// On Windows, TimeoutException is thrown when enabling the Loop. Refer issue: https://github.com/dotnet/maui/issues/29245
[Test, Order(2)]
[Category(UITestCategories.CarouselView)]
public void VerifyCarouselViewScrolling()
{
App.WaitForElement("carouselview");
App.SwipeRightToLeft("carouselview");
App.WaitForElement("Percentage View");
App.Tap("PositionButton");
VerifyScreenshot();
}
#endif
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading