Skip to content

Fix RemainingItemsThresholdReachedCommand not firing when CollectionVew has Header and Footer both defined #29618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Prev Previous commit
Update Issue29588.cs
  • Loading branch information
SuthiYuvaraj committed May 27, 2025
commit b955f414a0f7f1cab33be26ad216a08f8635007f
207 changes: 103 additions & 104 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue29588.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@
using System.Windows.Input;
using Microsoft.Maui.Controls;

namespace Microsoft.Maui.Controls.Issues
namespace Microsoft.Maui.Controls.Issues;

[Issue(IssueTracker.Github, 29588, "CollectionView RemainingItemsThresholdReachedcommand should trigger on scroll near end", PlatformAffected.Android)]
public class Issue29588 : ContentPage
{
[Issue(IssueTracker.Github, 29588, "CollectionView RemainingItemsThresholdReachedcommand should trigger on scroll near end", PlatformAffected.Android)]
public class Issue29588 : ContentPage
public Issue29588()
{
public Issue29588()
{
BindingContext = new Issue29588ViewModel();
BindingContext = new Issue29588ViewModel();

var thresholdLabel = new Label
{
AutomationId = "29588ThresholdLabel",
HorizontalOptions = LayoutOptions.Center,
HeightRequest = 50,
FontSize = 18
};
thresholdLabel.SetBinding(Label.TextProperty, nameof(Issue29588ViewModel.ThresholdStatus));

var collectionView = new CollectionView
var thresholdLabel = new Label
{
AutomationId = "29588ThresholdLabel",
HorizontalOptions = LayoutOptions.Center,
HeightRequest = 50,
FontSize = 18
};
thresholdLabel.SetBinding(Label.TextProperty, nameof(Issue29588ViewModel.ThresholdStatus));

var collectionView = new CollectionView
{
AutomationId = "29588CollectionView",
ItemsSource = ((Issue29588ViewModel)BindingContext).Items,
RemainingItemsThreshold = 1,
RemainingItemsThresholdReachedCommand = ((Issue29588ViewModel)BindingContext).RemainingItemReachedCommand,
Header = new Grid
{
AutomationId = "29588CollectionView",
ItemsSource = ((Issue29588ViewModel)BindingContext).Items,
RemainingItemsThreshold = 1,
RemainingItemsThresholdReachedCommand = ((Issue29588ViewModel)BindingContext).RemainingItemReachedCommand,
Header = new Grid
{
BackgroundColor = Colors.Bisque,
Children =
BackgroundColor = Colors.Bisque,
Children =
{
new Label
{
Expand All @@ -40,120 +40,119 @@ public Issue29588()
Text = "CollectionView does not fire RemainingItemsThresholdReachedCommand when Header and Footer both are set."
}
}
},
ItemTemplate = new DataTemplate(() =>
},
ItemTemplate = new DataTemplate(() =>
{
var label = new Label
{
var label = new Label
{
Margin = new Thickness(20, 30),
FontSize = 25
};
label.SetBinding(Label.TextProperty, ".");
return label;
}),
Margin = new Thickness(20, 30),
FontSize = 25
};
label.SetBinding(Label.TextProperty, ".");
return label;
}),

};
};

var activityIndicator = new ActivityIndicator
{
Margin = new Thickness(0, 20)
};
activityIndicator.SetBinding(ActivityIndicator.IsRunningProperty, "IsLoadingMore");
activityIndicator.SetBinding(ActivityIndicator.IsVisibleProperty, "IsLoadingMore");
collectionView.Footer = activityIndicator;
var activityIndicator = new ActivityIndicator
{
Margin = new Thickness(0, 20)
};
activityIndicator.SetBinding(ActivityIndicator.IsRunningProperty, "IsLoadingMore");
activityIndicator.SetBinding(ActivityIndicator.IsVisibleProperty, "IsLoadingMore");
collectionView.Footer = activityIndicator;


var grid = new Grid
{
Padding = 20,
RowDefinitions =
var grid = new Grid
{
Padding = 20,
RowDefinitions =
{
new RowDefinition { Height = 50 }, // Threshold label
new RowDefinition { Height = GridLength.Star } // CollectionView
}
};
};

grid.Add(thresholdLabel, 0, 0);
grid.Add(collectionView, 0, 1);
grid.Add(thresholdLabel, 0, 0);
grid.Add(collectionView, 0, 1);

Content = grid;
}
Content = grid;
}
}

public class Issue29588ViewModel : INotifyPropertyChanged
{
private bool _isLoadingMore;
private int _loadCount = 0;
private string thresholdStatus;
public class Issue29588ViewModel : INotifyPropertyChanged
{
private bool _isLoadingMore;
private int _loadCount = 0;
private string thresholdStatus;

public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();

public ICommand RemainingItemReachedCommand { get; }
public ICommand RemainingItemReachedCommand { get; }

public bool IsLoadingMore
public bool IsLoadingMore
{
get => _isLoadingMore;
set
{
get => _isLoadingMore;
set
if (_isLoadingMore != value)
{
if (_isLoadingMore != value)
{
_isLoadingMore = value;
OnPropertyChanged();
}
_isLoadingMore = value;
OnPropertyChanged();
}
}
public string ThresholdStatus
}
public string ThresholdStatus
{
get => thresholdStatus;
set
{
get => thresholdStatus;
set
if (thresholdStatus != value)
{
if (thresholdStatus != value)
{
thresholdStatus = value;
OnPropertyChanged();
}
thresholdStatus = value;
OnPropertyChanged();
}
}
}

public Issue29588ViewModel()
{
ThresholdStatus = "Threshold not reached";
RemainingItemReachedCommand = new Command(async () => await LoadMoreItemsAsync());
LoadInitialItems();
}
public Issue29588ViewModel()
{
ThresholdStatus = "Threshold not reached";
RemainingItemReachedCommand = new Command(async () => await LoadMoreItemsAsync());
LoadInitialItems();
}

private void LoadInitialItems()
private void LoadInitialItems()
{
for (int i = 1; i <= 20; i++)
{
for (int i = 1; i <= 20; i++)
{
Items.Add($"Item {i}");
}
Items.Add($"Item {i}");
}
}

private async Task LoadMoreItemsAsync()
{
if (IsLoadingMore)
return;

IsLoadingMore = true;

await Task.Delay(1500); // Simulate API call or long operation
private async Task LoadMoreItemsAsync()
{
if (IsLoadingMore)
return;

for (int i = 1; i <= 10; i++)
{
Items.Add($"Loaded Item {_loadCount * 10 + i + 20}");
}
IsLoadingMore = true;

_loadCount++;
IsLoadingMore = false;
ThresholdStatus = "Threshold reached";
}
await Task.Delay(1500); // Simulate API call or long operation

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
for (int i = 1; i <= 10; i++)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Items.Add($"Loaded Item {_loadCount * 10 + i + 20}");
}

_loadCount++;
IsLoadingMore = false;
ThresholdStatus = "Threshold reached";
}

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Loading