I am populating a ComboBox from an API. The API returns paginated data and there could be 100s of records. I dont want to load on the records at once so I want to load let’s say 25 records then when the user scrolls to the end of the ComboBox load the next 25 and so on. The poblem I am having is that I cannot detect the scroll even and call the api for the next set of records. Here is what I’ve tried so far.
ComboBox
<ComboBox
x:Name="BrandComboBox"
Header="Brand"
MaxWidth="500"
Width="500"
Margin="0,0,0,20"
HorizontalAlignment="Left"
ItemsSource="{x:Bind BrandsList, Mode=OneWay}"
DisplayMemberPath="Name"
SelectionChanged="BrandComboBox_SelectionChanged"
Loaded="BrandComboBox_Loaded"
DropDownOpened="BrandComboBox_DropDownOpened"
PlaceholderText="Select brand" />
Code behind
public sealed partial class AddProductPage: Page {
private readonly ApiService _apiService;
private readonly ILogger < AddProductPage > _logger;
private readonly string _apiVersion = "1.0";
private string _dialogTitle;
private string _dialogMessage;
public event PropertyChangedEventHandler PropertyChanged;
private int _currentBrandPage = 1;
private int _totalBrandPages = 1; // Initialize with 1 or based on your API metadata
private int _pageSize = 20;
private bool _isLoadingMoreBrands = false;
private ObservableCollection < Models.Brand.Brand > BrandsList {
get;
set;
} = new ObservableCollection < Models.Brand.Brand > ();
/// <summary>
/// Initializes a new instance of the <see cref="AddProduct"/> class.
/// </summary>
public AddProductPage() {
this.InitializeComponent();
DataContext = this;
_apiService = new ApiService();
_logger = App.ServiceProvider.GetService < ILogger < AddProductPage >> ();
}
private async void BrandComboBox_Loaded(object sender, RoutedEventArgs e) {
// Load initial set of brands
await LoadBrandsAsync(_currentBrandPage, _pageSize);
}
// Handle mouse scroll event
private void BrandComboBoxScrollViewer_PointerWheelChanged(object sender, PointerRoutedEventArgs e) {
var scrollViewer = sender as ScrollViewer;
if (scrollViewer == null) return;
// Check if scroll is nearing the bottom
if (!_isLoadingMoreBrands && scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight * 0.9) {
LoadMoreBrandsIfNeeded();
}
}
private void BrandComboBoxScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) {
var scrollViewer = sender as ScrollViewer;
if (scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight) {
// Load more items when scrolled to the bottom
LoadMoreBrandsIfNeeded();
}
}
private async void BrandComboBox_DropDownOpened(object sender, object e) {
// Delay to ensure the Popup and its content are fully loaded
await Task.Delay(100);
// Get the ItemsPresenter within the ComboBox
var itemsPresenter = BrandComboBox.FindDescendant < ItemsPresenter > ();
if (itemsPresenter != null) {
itemsPresenter.Loaded -= ItemsPresenter_Loaded; // Avoid multiple subscriptions
itemsPresenter.Loaded += ItemsPresenter_Loaded;
}
}
private void ItemsPresenter_Loaded(object sender, RoutedEventArgs e) {
var itemsPresenter = sender as ItemsPresenter;
var scrollViewer = itemsPresenter?.FindDescendant < ScrollViewer > ();
if (scrollViewer != null) {
scrollViewer.ViewChanged += BrandComboBoxScrollViewer_ViewChanged;
}
}
private ScrollViewer FindScrollViewer(DependencyObject parent) {
if (parent is ScrollViewer) {
return (ScrollViewer) parent;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) {
var child = VisualTreeHelper.GetChild(parent, i);
var result = FindScrollViewer(child);
if (result != null) {
return result;
}
}
return null;
}
private async Task LoadBrandsAsync(int page, int pageSize) {
try {
string method = $ "api/Brands?page={page}&pageSize={pageSize}";
var response = await _apiService.GetAsync(method, _apiVersion);
if (response?.IsSuccessStatusCode == true) {
var responseString = await response.Content.ReadAsStringAsync();
var paginatedResponse = JsonConvert.DeserializeObject < PaginatedResponse < Models.Brand.Brand >> (responseString);
if (paginatedResponse != null && paginatedResponse.TotalRecords > 1) {
foreach(var brand in paginatedResponse.Data) {
BrandsList.Add(brand);
}
// Update total pages based on the API response
_totalBrandPages = (paginatedResponse.TotalRecords + pageSize - 1) / pageSize;
}
}
} catch (Exception ex) {
_logger.LogError(ex, "An error occurred while loading brands.");
_dialogTitle = _languageFileContent?.dialog?.error ?? "Error";
_dialogMessage = _languageFileContent?.message?.error?.unexpectedError ?? "An unexpected error occured.";
await ErrorDialog.ShowErrorDialogAsync(_dialogTitle, _dialogMessage, this.XamlRoot);
}
}
private async void LoadMoreBrandsIfNeeded() {
if (_currentBrandPage < _totalBrandPages) {
_isLoadingMoreBrands = true;
_currentBrandPage++;
await LoadBrandsAsync(_currentBrandPage, _pageSize);
_isLoadingMoreBrands = false;
}
}
private void BrandComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
// Handle selection change logic here if needed
}
}
Any suggestion on how to accomplish this would be greatly appreciated. Thank you in advance.