You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Maui CollectionView滚动加载重复对象问题求助

.NET Maui CollectionView滚动时项重复显示问题

问题现象

在.NET Maui应用的Android设备/虚拟机上,滚动CollectionView时出现项内容重复:初始显示正常,但滚动后,原本该显示Product 4的位置重复显示Product 3,Product 5的位置重复显示Product 1,且交互效果对应原项。移除DataTemplate中的子CollectionView(UOFMs选择)或Picker后显示恢复正常,重新添加任一控件问题复现。

相关代码

CollectionView的XAML代码

<CollectionView x:Name="cartCollectionView" ZIndex="0"
                ItemsSource="{Binding CartItems}"
                CanReorderItems="False"
                SelectionMode="None"
                Margin="0, 10"
                Grid.Row="0">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical" />
    </CollectionView.ItemsLayout>

    <CollectionView.ItemTemplate>
        <DataTemplate>
          <Border Stroke="LightGray" Margin="0, 5" Padding="10">
            <Border.StrokeShape>
              <RoundRectangle CornerRadius="5" />
            </Border.StrokeShape>
            <Grid ColumnSpacing="10" RowSpacing="5">
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
              </Grid.RowDefinitions>

              <Grid Grid.Row="0">
                <Label
 Text="{Binding Name}" 
 FontSize="18"
 VerticalOptions="Center" />
              </Grid>
              <CollectionView Grid.Row="1" SelectionMode="Single" SelectedItem="{Binding selectedUOFM}" ItemsSource="{Binding UOFMs}" SelectionChanged="CollectionView_SelectionChanged">
                <CollectionView.ItemsLayout>
                  <LinearItemsLayout Orientation="Horizontal" ItemSpacing="10"/>
                </CollectionView.ItemsLayout>

                <CollectionView.ItemTemplate>
                  <DataTemplate x:DataType="x:String">
                    <Border Stroke="DarkGray" Padding="10">
                      <Border.StrokeShape>
                        <RoundRectangle CornerRadius="20"/>
                      </Border.StrokeShape>
                      <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup Name="CommonStates">
                          <VisualState Name="Normal">
                            <VisualState.Setters>
                              <Setter Property="BackgroundColor" Value="Transparent"></Setter>
                            </VisualState.Setters>
                          </VisualState>
                          <VisualState Name="Selected">
                            <VisualState.Setters>
                              <Setter Property="BackgroundColor" Value="#6174b7"></Setter>
                            </VisualState.Setters>
                          </VisualState>
                        </VisualStateGroup>
                      </VisualStateManager.VisualStateGroups>

                      <Label Text="{Binding}" VerticalOptions="Center" HorizontalOptions="Center" />
                    </Border>
                  </DataTemplate>
                </CollectionView.ItemTemplate>
              </CollectionView>

              <Grid Grid.Row="2" ColumnDefinitions="Auto, Auto, Auto, Auto, *">
                <Label VerticalOptions="Center" Grid.Column="0" Text="Lot: "/>
                <Label VerticalOptions="Center" Grid.Column="1" Text="{Binding selectedLot.lottext}"/>
                <Label VerticalOptions="Center" Text="{x:Static local:AppResources.ExpirationDate}" Grid.Column="2" Margin="10,0,0,0"/>
                <Picker SelectedItem="{Binding selectedLot}" x:Name="pickerLot" Grid.Column="3" WidthRequest="1" Opacity="0" Loaded="Picker_Loaded"
        ItemsSource="{Binding lots}" 
        ItemDisplayBinding="{Binding pickertext}"
        Title="{x:Static local:AppResources.SelectDateLot}"
        HorizontalOptions="StartAndExpand"
        SelectedIndexChanged="Picker_SelectedIndexChanged"/>
                <Label Text="{Binding selectedLot.expirationDateText}" Grid.Column="3" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"  />
                <Button Text="..." Grid.Column="4" x:Name="bPickLot" Clicked="bPickLot_Clicked" HorizontalOptions="End" IsVisible="{Binding IsPickLotVisible}" BackgroundColor="#6174b7" CornerRadius="5" HeightRequest="5"/>
              </Grid>

              <Grid Grid.Row="3" ColumnDefinitions="Auto, Auto, Auto, *, Auto">
                <Label VerticalOptions="Center" Grid.Column="0">
                  <Label.FormattedText>
                    <FormattedString>
                      <Span Text="{x:Static local:AppResources.Qty}" />
                      <Span Text=":" />
                    </FormattedString>
                  </Label.FormattedText>
                </Label>
                <Entry Text="{Binding quantity}" Grid.Column="1" 
Unfocused="Entry_Unfocused"
Keyboard="Numeric" 
HorizontalOptions="StartAndExpand" 
WidthRequest="50" />

                <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center" Grid.Column="2">
                  <Label Text="{Binding SalePrice, StringFormat='{0:C}'}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                  <Label Text="{Binding Price, StringFormat='{0:C}'}" IsVisible="{Binding IsSale}" TextColor="Red" TextDecorations="Strikethrough" Margin="5,0,0,0" FontSize="16" HorizontalOptions="End" VerticalOptions="Center" />
                </StackLayout>

                <Label Text="{Binding ItemTotal, StringFormat='{0:C}'}" FontSize="16" Grid.Column="3"  HorizontalOptions="Center" VerticalOptions="Center" />

                <ImageButton Grid.Column="4"
VerticalOptions="Center"
HorizontalOptions="Center"
HeightRequest="15"
WidthRequest="15"
Source="x.png"
Clicked="ImageButton_Clicked"/>
              </Grid>
            </Grid>
          </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

项加载逻辑

private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
    await ViewModel?.LoadCartItems();
}

ViewModel相关代码

private List<CartItem> _cartItems { get; set; }
public List<CartItem> CartItems
{
    get => _cartItems;
    set
    {
        _cartItems = value;
        OnPropertyChanged(nameof(CartItems));
        OnPropertyChanged(nameof(Total));
    }
}

public async Task LoadCartItems()
{
    CartItems = await databaseHelper.GetCartItems();
}

数据库助手GetCartItems方法

// return test objects, no need to fetch the DB to reproduce bug
public async Task<List<CartItem>> GetCartItems()
{
    return new List<CartItem>()
               {
                   new CartItem()
                       {
                           itemnmbr = "12345",
                           nameFR = "Produit 1",
                           nameEN = "Product 1",
                           quantity = 1,
                           selectedUOFM = "kg",
                           prices = new List<ProductPrice>
                                    {
                                        new ProductPrice { itemnmbr = "12345", price = 10.0m, uofm = "kg" }
                                    },
                           lots = new List<LotEntity>
                                  {
                                      new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                                  },
                           selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                       },
                   new CartItem()
                       {
                           itemnmbr = "12345",
                           nameFR = "Produit 2",
                           nameEN = "Product 2",
                           quantity = 2,
                           selectedUOFM = "kg",
                           prices = new List<ProductPrice>
                           {
                               new ProductPrice { itemnmbr = "12345", price = 20.0m, uofm = "kg" }
                           },
                           lots = new List<LotEntity>
                           {
                               new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                           },
                           selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                       },
                   new CartItem()
                       {
                           itemnmbr = "12345",
                           nameFR = "Produit 3",
                           nameEN = "Product 3",
                           quantity = 3,
                           selectedUOFM = "kg",
                           prices = new List<ProductPrice>
                           {
                               new ProductPrice { itemnmbr = "12345", price = 30.0m, uofm = "kg" }
                           },
                           lots = new List<LotEntity>
                           {
                               new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                           },
                           selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                       },
                   new CartItem()
                       {
                           itemnmbr = "12345",
                           nameFR = "Produit 4",
                           nameEN = "Product 4",
                           quantity = 4,
                           selectedUOFM = "kg",
                           prices = new List<ProductPrice>
                           {
                               new ProductPrice { itemnmbr = "12345", price = 40.0m, uofm = "kg" }
                           },
                           lots = new List<LotEntity>
                           {
                               new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                           },
                           selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                      },
                  new CartItem()
                      {
                          itemnmbr = "12345",
                          nameFR = "Produit 5",
                          nameEN = "Product 5",
                          quantity = 5,
                          selectedUOFM = "kg",
                          prices = new List<ProductPrice>
                          {
                              new ProductPrice { itemnmbr = "12345", price = 50.0m, uofm = "kg" }
                          },
                          lots = new List<LotEntity>
                          {
                              new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                          },
                          selectedLot = new LotEntity { itemnmbr = "12345", lot = "Lot 1", expirationDate = "2024-12-31", isSelected = true }
                     }
     };
}

解决方案

1. 给根DataTemplate指定x:DataType

在CollectionView的根DataTemplate上添加x:DataType,明确绑定的实体类型,帮助Maui绑定系统正确识别数据上下文,避免视图复用时的绑定混乱:

<DataTemplate x:DataType="local:CartItem">

(注意替换local为你的CartItem类所在的命名空间)

2. 确保子控件绑定为双向模式

对于子CollectionView的SelectedItem,显式声明双向绑定,确保视图复用能正确同步数据:

SelectedItem="{Binding selectedUOFM, Mode=TwoWay}"

3. 将List替换为ObservableCollection

ViewModel中把List<CartItem>改为ObservableCollection<CartItem>,集合的变更会自动通知UI更新,避免视图复用时的数据不匹配:

private ObservableCollection<CartItem> _cartItems { get; set; }
public ObservableCollection<CartItem> CartItems
{
    get => _cartItems;
    set
    {
        _cartItems = value;
        OnPropertyChanged(nameof(CartItems));
        OnPropertyChanged(nameof(Total));
    }
}

public async Task LoadCartItems()
{
    var items = await databaseHelper.GetCartItems();
    CartItems = new ObservableCollection<CartItem>(items);
}

4. 检查实体类的INotifyPropertyChanged实现

确保CartItem、LotEntity等所有绑定的实体类正确实现INotifyPropertyChanged接口,所有属性变更时触发OnPropertyChanged事件,保证UI能实时刷新数据:

public class CartItem : INotifyPropertyChanged
{
    private string _nameEN;
    public string nameEN
    {
        get => _nameEN;
        set
        {
            _nameEN = value;
            OnPropertyChanged(nameof(nameEN));
            OnPropertyChanged(nameof(Name)); // 如果Name是计算属性,也要触发其变更
        }
    }

    // 其他属性同理实现...

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

5. 临时禁用视图复用(不推荐长期使用)

如果上述方法无效,可临时修改CollectionView的缓存策略禁用视图复用,该方法会影响列表性能,仅作为临时调试方案:

<CollectionView x:Name="cartCollectionView" 
                ...
                CachingStrategy="RetainElement">

内容的提问来源于stack exchange,提问作者MyselfAndOnlyMe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:04:49