.NET MAUI中CollectionView无法显示API返回数据的问题排查
问题背景
升级到.NET MAUI后,API可正常获取数据,但ObservableCollection中的内容无法在CollectionView中展示。此前用ListView可正常显示,切换后无报错但无内容,API每次返回10条记录(Postman和旧应用验证正常)。
相关代码
XAML 代码
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SaleBeaconMobile.Views.MainPageCus" Title="Sale Alerts" xmlns:ViewModels="clr-namespace:SaleBeaconMobile.ViewModels"> <ContentPage.BindingContext> <ViewModels:MainPageCusViewModel /> </ContentPage.BindingContext> <ContentPage.ToolbarItems> <ToolbarItem Text="Debug" Clicked="ToolbarItem_Clicked_3" Order="Secondary" /> <ToolbarItem Text="Settings" Clicked="ToolbarItem_Clicked" Order="Secondary"/> <ToolbarItem Text="Support" Clicked="ToolbarItem_Clicked_1" Order="Secondary"/> <ToolbarItem Text="Log Out" Clicked="ToolbarItem_Clicked_2" Order="Secondary"/> </ContentPage.ToolbarItems> <ContentPage.Content> <RefreshView> <CollectionView x:Name="CVSaleAlerts" ItemsSource="{Binding Items}" ItemsLayout="VerticalList" RemainingItemsThreshold="4" RemainingItemsThresholdReachedCommand="{Binding LoadMoreItemsCommand}" HeightRequest="300"> <CollectionView.ItemTemplate> <DataTemplate> <Grid Padding="10"> <Grid.RowDefinitions> <RowDefinition Height="280" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="350" /> </Grid.ColumnDefinitions> <Image Source="{Binding bg}" Grid.Row="0" Grid.Column="0" Aspect="AspectFill" WidthRequest="350" HeightRequest="280" MinimumHeightRequest="280" VerticalOptions="FillAndExpand" /> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </RefreshView> </ContentPage.Content> </ContentPage>
ViewModel 代码
using SaleBeaconMobile.Models; using SaleBeaconMobile.Services; using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows.Input; using Microsoft.Maui.Controls.Xaml; namespace SaleBeaconMobile.ViewModels { public class MainPageCusViewModel : BaseViewModel { private readonly DataService _dataService = new DataService(); private const int PageSize = 10; private int _currentPage = 0; // Track the current page for loading more data public ObservableCollection<Sale> Items { get; } private bool _isRefreshing; public bool IsRefreshing { get => _isRefreshing; set { _isRefreshing = value; OnPropertyChanged(); } } public ICommand LoadMoreItemsCommand { get; } public ICommand RefreshCommand => new Command(async () => await DownloadDataAsync()); public MainPageCusViewModel() { Items = new ObservableCollection<Sale>(); // Assign the command to LoadMoreItemsCommand LoadMoreItemsCommand = new Command(async () => await LoadMoreItemsAsync()); // Trigger initial data load Task.Run(async () => await DownloadDataAsync()); } // Method to load initial data public async Task DownloadDataAsync() { try { IsRefreshing = true; // Fetch initial data (page 0 or starting page) var items = await _dataService.GetItemsAsync(0, PageSize); // Clear existing items and add new items Items.Clear(); foreach (var item in items) { Items.Add(item); } // Update the current page _currentPage = 1; // Assuming pages start from 0 } catch (Exception ex) { // Handle exceptions as needed } finally { IsRefreshing = false; } } // Method to load more items for pagination or infinite scrolling public async Task LoadMoreItemsAsync() { try { if (IsRefreshing) return; // Prevent re-entry IsRefreshing = true; // Fetch more data based on the current page var items = await _dataService.GetItemsAsync(_currentPage, PageSize); // Add new items to the existing collection foreach (var item in items) { Items.Add(item); } // Increment the current page for the next load _currentPage++; } catch (Exception ex) { // Handle exceptions as needed } finally { IsRefreshing = false; } } } }
排查步骤
1. 补全RefreshView的绑定属性
你的RefreshView未绑定Command和IsRefreshing,这会导致刷新状态无法同步,甚至影响CollectionView的布局更新。修改XAML中的RefreshView:
<RefreshView Command="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}"> <!-- 原CollectionView内容 --> </RefreshView>
2. 移除CollectionView的固定高度约束
你给CollectionView设置了HeightRequest="300",如果数据项总高度超出这个值,或者数据加载时布局计算异常,会导致内容无法显示。建议移除固定高度,让控件自适应父布局:
<CollectionView x:Name="CVSaleAlerts" ItemsSource="{Binding Items}" ItemsLayout="VerticalList" RemainingItemsThreshold="4" RemainingItemsThresholdReachedCommand="{Binding LoadMoreItemsCommand}"> <!-- 原ItemTemplate内容 --> </CollectionView>
同时确保ContentPage的Content布局设置为VerticalOptions="FillAndExpand",让CollectionView能占满可用空间。
3. 验证数据绑定和集合状态
- 确认
BaseViewModel正确实现INotifyPropertyChanged接口,确保绑定上下文的变更能通知到UI。 - 在
DownloadDataAsync方法中添加调试日志,确认数据已正确加入集合:
查看输出面板,确认集合数量是否正确。foreach (var item in items) { Items.Add(item); System.Diagnostics.Debug.WriteLine($"Added item: {item.bg}, total items in collection: {Items.Count}"); }
4. 测试DataTemplate的绑定有效性
暂时在DataTemplate中添加一个Label,验证数据项的绑定是否生效:
<DataTemplate> <Grid Padding="10"> <Image Source="{Binding bg}" Grid.Row="0" Grid.Column="0" Aspect="AspectFill" WidthRequest="350" HeightRequest="280" MinimumHeightRequest="280" VerticalOptions="FillAndExpand" /> <Label Text="{Binding bg}" Grid.Row="0" Grid.Column="0" TextColor="Red" FontSize="Large"/> </Grid> </DataTemplate>
如果Label能显示内容,说明绑定没问题,问题可能出在Image的Source加载(比如bg不是有效的图片路径/资源名);如果Label也不显示,说明数据项的绑定存在问题,检查Sale类的bg属性是否为公共可访问的。
5. 修正初始数据加载的线程方式
ViewModel构造函数中使用Task.Run(async () => await DownloadDataAsync())可能导致UI线程上下文异常,建议改为直接调用:
// 替换原Task.Run代码 _ = DownloadDataAsync();
或者在页面的OnAppearing方法中触发加载,确保在UI线程上下文执行:
// 在MainPageCus.xaml.cs中添加 protected override async void OnAppearing() { base.OnAppearing(); if (BindingContext is MainPageCusViewModel vm) { await vm.DownloadDataAsync(); } }
6. 暂时禁用分页相关属性
先移除RemainingItemsThreshold和RemainingItemsThresholdReachedCommand,排除分页逻辑对初始数据显示的干扰,确认初始数据能正常显示后再恢复分页功能。
内容的提问来源于stack exchange,提问作者Joey007

