diff --git a/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs b/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs index 6b08e60c658b..c45e9c315ba2 100644 --- a/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs +++ b/src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs @@ -149,7 +149,7 @@ protected override ItemsViewScrolledEventArgs ComputeVisibleIndexes(ItemsViewScr { args = base.ComputeVisibleIndexes(args, orientation, advancing); - if (ItemsView.Loop && ItemsView.ItemsSource is not null) + if (ItemsView.Loop && ItemsView.ItemsSource is not null && ItemCount > 0) { args.FirstVisibleItemIndex %= ItemCount; args.CenterItemIndex %= ItemCount; @@ -159,6 +159,21 @@ protected override ItemsViewScrolledEventArgs ComputeVisibleIndexes(ItemsViewScr return args; } + protected override void UpdateEmptyViewVisibility() + { + if (ItemsView?.Loop == true) + { + bool isEmpty = (CollectionViewSource?.View?.Count ?? 0) == 0; + var targetTemplate = isEmpty ? null : CarouselItemsViewTemplate; + if (ListViewBase.ItemTemplate != targetTemplate) + { + ListViewBase.ItemTemplate = targetTemplate; + } + } + + base.UpdateEmptyViewVisibility(); + } + ListViewBase CreateCarouselListLayout(ItemsLayoutOrientation layoutOrientation) { UI.Xaml.Controls.ListView listView; diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 65e1a3eca2b1..e24d5a76d14f 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -4,5 +4,6 @@ override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? property ~override Microsoft.Maui.Controls.RadioButton.OnPropertyChanged(string propertyName = null) -> void override Microsoft.Maui.Controls.GraphicsView.OnBindingContextChanged() -> void override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void +override Microsoft.Maui.Controls.Handlers.Items.CarouselViewHandler.UpdateEmptyViewVisibility() -> void ~override Microsoft.Maui.Controls.SwipeItems.OnPropertyChanged(string propertyName = null) -> void Microsoft.Maui.Controls.Label.~Label() -> void diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyCarouselViewEmptyView.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyCarouselViewEmptyView.png new file mode 100644 index 000000000000..254ecc3ee496 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyCarouselViewEmptyView.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue7150.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue7150.cs new file mode 100644 index 000000000000..b5b4c849f681 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue7150.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.ObjectModel; +using System.Windows.Input; + +namespace Maui.Controls.Sample.Issues; +[Issue(IssueTracker.Github, 7150, "EmptyView using Template displayed at the same time as the content", PlatformAffected.UWP)] +public class Issue7150 : TestContentPage +{ + public Issue7150() + { + Title = "Issue 7150"; + BindingContext = new Issue7150ViewModel(); + } + protected override void Init() + { + var filterButton = new Button + { + Margin = new Thickness(20), + AutomationId = "FilterButton", + Text = "Filter" + }; + filterButton.SetBinding(Button.CommandProperty, "FilterCommand"); + + var emptyViewContent = new StackLayout + { + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Start, + Children = + { + new Label + { + Text = "No results matched your filter.", + Margin = new Thickness(10, 25, 10, 10), + FontAttributes = FontAttributes.Bold, + FontSize = 18, + HorizontalTextAlignment = TextAlignment.Center + }, + new Label + { + Text = "Try a broader filter?", + FontAttributes = FontAttributes.Italic, + FontSize = 12, + HorizontalTextAlignment = TextAlignment.Center + } + } + }; + + var emptyView = new ContentView { Content = emptyViewContent }; + var carouselView = new CarouselView + { + ItemTemplate = GetCarouselTemplate(), + EmptyView = emptyView, + }; + + carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Items"); + var grid = new Grid + { + RowDefinitions = new RowDefinitionCollection + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Star } + } + }; + + grid.Add(filterButton, 0, 0); + grid.Add(carouselView, 0, 1); + Content = grid; + } + + internal DataTemplate GetCarouselTemplate() + { + return new DataTemplate(() => + { + var grid = new Grid(); + var info = new Label + { + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + Margin = new Thickness(6) + }; + + info.SetBinding(Label.TextProperty, new Binding("Name")); + grid.Children.Add(info); + return grid; + }); + } + + public class Issue7150Model + { + public string Name { get; set; } + } + + public class Issue7150ViewModel : BindableObject + { + ObservableCollection _items; + public ICommand FilterCommand => new Command(FilterItems); + readonly IList source; + + public Issue7150ViewModel() + { + source = new List(); + source.Add(new Issue7150Model + { + Name = "Baboon" + }); + source.Add(new Issue7150Model + { + Name = "Capuchin Monkey" + }); + source.Add(new Issue7150Model + { + Name = "Blue Monkey" + }); + + Items = new ObservableCollection(source); + } + + public ObservableCollection Items + { + get { return _items; } + set + { + _items = value; + OnPropertyChanged(); + } + } + + public void FilterItems() + { + var filter = "Mandrill"; + var filteredItems = source.Where(monkey => monkey.Name?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false).ToList(); + foreach (var monkey in source) + { + if (!filteredItems.Contains(monkey)) + { + Items?.Remove(monkey); + } + else + { + if (Items != null && !Items.Contains(monkey)) + { + Items.Add(monkey); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifyCarouselViewEmptyView.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifyCarouselViewEmptyView.png new file mode 100644 index 000000000000..c5b6ea27c383 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifyCarouselViewEmptyView.png differ diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7150.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7150.cs new file mode 100644 index 000000000000..4641936aa803 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7150.cs @@ -0,0 +1,22 @@ +#if TEST_FAILS_ON_WINDOWS // Related issue for windows: https://github.com/dotnet/maui/issues/29245 +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; +public class Issue7150 : _IssuesUITest +{ + public Issue7150(TestDevice device) : base(device) { } + + public override string Issue => "EmptyView using Template displayed at the same time as the content"; + + [Test] + [Category(UITestCategories.CarouselView)] + public void VerifyCarouselViewEmptyView() + { + App.WaitForElement("FilterButton"); + App.Tap("FilterButton"); + VerifyScreenshot(); + } +} +#endif \ No newline at end of file diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyCarouselViewEmptyView.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyCarouselViewEmptyView.png new file mode 100644 index 000000000000..74626996ad12 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyCarouselViewEmptyView.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyCarouselViewEmptyView.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyCarouselViewEmptyView.png new file mode 100644 index 000000000000..a32ca0540ea8 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyCarouselViewEmptyView.png differ