MAUI CollectionView替换项时仅Windows端滚动至顶部问题
解决Windows端CollectionView滚动重置问题的两种方案
方案1:使用DataTrigger替代DataTemplateSelector(推荐)
避免替换集合项,通过数据触发自动切换UI状态,不会触发CollectionView的滚动重置:
- 修改XAML,移除
DataTemplateSelector,改用带DataTrigger的单一DataTemplate:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard" xmlns:viewmodel="clr-namespace:YourNamespace.ViewModels" x:Class="YourNamespace.Pages.FlatRatePage"> <ContentPage.Resources> <DataTemplate x:Key="FlatRateItemTemplate" x:DataType="viewmodel:FlatRateUI"> <Grid Padding="10" Margin="0,5,0,0"> <!-- 通过DataTrigger切换背景色 --> <Grid.Style> <Style TargetType="Grid"> <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource FlatRateBGNoItemsLight}, Dark={StaticResource FlatRateBGNoItemsDark}}"/> <Style.Triggers> <DataTrigger TargetType="Grid" Binding="{Binding Qty}" Value="{x:Static sys:Int32.Zero}" Comparison="GreaterThan"> <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource FlatRateBGHasItemsLight}, Dark={StaticResource FlatRateBGHasItemsDark}}"/> </DataTrigger> </Style.Triggers> </Style> </Grid.Style> <!-- 商品名称等公共内容 --> <Label Text="{Binding ItemName}" VerticalOptions="Center"/> <!-- Add按钮 --> <Button Text="Add" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:YourViewModel}}, Path=AddCommand}" CommandParameter="{Binding .}" HorizontalOptions="End" VerticalOptions="Center"/> <!-- Remove按钮:仅在Qty>0时显示 --> <Button Text="Remove" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:YourViewModel}}, Path=RemoveCommand}" CommandParameter="{Binding .}" HorizontalOptions="End" VerticalOptions="Center" Margin="0,0,80,0"> <Button.Style> <Style TargetType="Button"> <Setter Property="IsVisible" Value="False"/> <Style.Triggers> <DataTrigger TargetType="Button" Binding="{Binding Qty}" Value="{x:Static sys:Int32.Zero}" Comparison="GreaterThan"> <Setter Property="IsVisible" Value="True"/> </DataTrigger> </Style.Triggers> </Style> </Button.Style> </Button> </Grid> </DataTemplate> </ContentPage.Resources> <VerticalStackLayout> <HorizontalStackLayout> <Button x:Name="BackButton" Text="< Back" VerticalOptions="Center" Clicked="OnBackButtonClicked" HorizontalOptions="Center"/> <Button x:Name="CartButton" Text="Cart" VerticalOptions="Center" Clicked="OnCartButtonClicked" HorizontalOptions="Center"/> <Button x:Name="SCButton" Text="SC" VerticalOptions="Center" Clicked="OnSCButtonClicked" HorizontalOptions="Center"/> </HorizontalStackLayout> <CollectionView ItemsSource="{Binding Items}" ItemTemplate="{StaticResource FlatRateItemTemplate}" VerticalOptions="Fill" HeightRequest="680" HorizontalOptions="Fill"/> </VerticalStackLayout> </ContentPage>
- 修改
FlatRateUI实现INotifyPropertyChanged,确保Qty变化时触发属性通知:
using System.ComponentModel; using System.Runtime.CompilerServices; namespace YourNamespace.ViewModels { public class FlatRateUI : INotifyPropertyChanged { private int _qty; public string ItemName { get; set; } public int Qty { get => _qty; set { if (_qty != value) { _qty = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
- 简化Add/Remove命令,无需替换集合项:
[RelayCommand] async Task Add(FlatRateUI i) { System.Diagnostics.Debug.WriteLine($"{i.ItemName} {i.Qty}"); i.Qty += 1; } [RelayCommand] async Task Remove(FlatRateUI i) { System.Diagnostics.Debug.WriteLine($"{i.ItemName} {i.Qty}"); if (i.Qty > 0) { i.Qty -= 1; } }
方案2:保留DataTemplateSelector,手动保存并恢复滚动位置
如果必须使用原有的DataTemplateSelector方式,需要在替换集合项前后保存滚动位置,替换后恢复:
- 在XAML中给CollectionView命名:
<CollectionView x:Name="ItemsCollectionView" ItemsSource="{Binding Items}" ItemTemplate="{StaticResource FlatRateItemSelector}" VerticalOptions="Fill" HeightRequest="680" HorizontalOptions="Fill"/>
- 修改Add/Remove命令,保存并恢复滚动位置:
[RelayCommand] async Task Add(FlatRateUI i) { System.Diagnostics.Debug.WriteLine($"{i.ItemName} {i.Qty}"); i.Qty += 1; if (i.Qty == 1) { // 保存当前滚动位置 var scrollPosition = ItemsCollectionView.ScrollY; int idx = Items.IndexOf(i); Items[idx] = i; // 等待UI更新后恢复滚动位置 await Task.Delay(100); ItemsCollectionView.ScrollToAsync(scrollPosition, ScrollToPosition.Start, false); } } [RelayCommand] async Task Remove(FlatRateUI i) { System.Diagnostics.Debug.WriteLine($"{i.ItemName} {i.Qty}"); if (i.Qty > 0) { i.Qty -= 1; if (i.Qty == 0) { // 保存当前滚动位置 var scrollPosition = ItemsCollectionView.ScrollY; int idx = Items.IndexOf(i); Items[idx] = i; // 等待UI更新后恢复滚动位置 await Task.Delay(100); ItemsCollectionView.ScrollToAsync(scrollPosition, ScrollToPosition.Start, false); } } }
注意:这种方式依赖Task.Delay,稳定性不如方案1,仅作为兼容原有代码的备选方案。
内容的提问来源于stack exchange,提问作者Joelxxx
相关产品推荐
相关产品推荐

