将新项添加到 ObservableCollection(绑定到 .net Maui 中的集合视图)时显示重复项<>



我目前有一个集合视图,显示从我的设备中的本地数据库中提取的记录列表。数据库工作正常,添加和删除记录工作正常。问题是,当我添加或删除一条记录时,当刷新集合视图时,它会显示每个现有记录的副本。奇怪的部分是,如果我再次刷新,它会恢复正常,只显示从数据库中提取的表的记录。

这是我的视图:

[QueryProperty(nameof(Players), "Players")]
public partial class ManagePlayersPageViewModel : ObservableObject
{
/// <summary>
/// List of players being displayed 
/// </summary>
private ObservableCollection<Player> _players = new();
public ObservableCollection<Player> Players
{
get => _players;
set => SetProperty(ref _players, value);
}
[ObservableProperty] private bool isRefreshing;
/// <summary>   
/// Options for selection modes
/// </summary>
public SelectionOptions SelectionOptions { get; } = new();
/// <summary>
/// Adds player to list
/// </summary>
/// <returns></returns>
[RelayCommand]
async Task AddPlayer()
{
var task =  await Shell.Current.ShowPopupAsync(new AddPlayerPopup());
var player = task as Player;
if (task == null)
return;
if (await PlayerService.RecordExists(player))
{
await Shell.Current.DisplaySnackbar("Jugador ya existe");
return;
}

await PlayerService.AddAsync(player);


await Refresh();
} 

下面是refresh()方法:

/// <summary>
/// Refreshs and updates UI after each database query 
/// </summary>
/// <returns></returns>
[RelayCommand]
async Task Refresh()
{
IsRefreshing = true;
await Task.Delay(TimeSpan.FromSeconds(1));
Players.Clear();
var playersList = await PlayerService.GetPlayersAsync();

foreach (Player player in playersList)
Players.Add(player);
IsRefreshing = false;
}

这里是我的xaml控件所在的位置:

<RefreshView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CollectionView
ItemsSource="{Binding Players}"
SelectionMode="{Binding SelectionOptions.SelectionMode}">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItemView 
Padding="0, 2.5"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:ManagePlayersPageViewModel}}, Path= DeletePlayerCommand}"
CommandParameter="{Binding .}">
<Border 
StrokeShape="RoundRectangle 10"
Stroke="{StaticResource DimBlackSolidColorBrush}"
Background="{StaticResource DimBlackSolidColorBrush}">
<Grid>
<Image
Source="Resources/Images/delete.svg"
WidthRequest="35"
HeightRequest="35"
Aspect="AspectFill"/>
</Grid>
</Border>
</SwipeItemView>
</SwipeItems>
</SwipeView.RightItems>
<Grid>
<Border Grid.Column="0"
StrokeShape="RoundRectangle 10"
Stroke="{StaticResource DimBlackSolidColorBrush}"
StrokeThickness="3">
<Grid
RowDefinitions="auto, auto, auto"
Background="{StaticResource DimBlackSolidColorBrush}">
<Label Grid.Row="0"
Text="{Binding Name}"
VerticalTextAlignment="Center"
Margin="10, 2.5" 
TextColor="White"/>
<Label Grid.Row="1"
Text="{Binding Alias}"
VerticalTextAlignment="Center"
Margin="10, 2.5" />
<Label Grid.Row="2"
Text="{Binding Team, TargetNullValue=Ninguno}"
VerticalTextAlignment="Center"
FontAttributes="Italic"
Margin="10, 2.5" />
</Grid>
</Border>
<Grid.GestureRecognizers>
<TapGestureRecognizer 
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:ManagePlayersPageViewModel}}, Path=ItemTappedCommand}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>

知道为什么会这样吗?注意:数据库是在前一个页面中查询的,并且作为参数传递给集合视图所在的页面,不知道这是否与它有任何关系。注意:它过去可以很好地与列表视图控件一起工作,但我没有那么多的灵活性来定制该控件,这就是为什么我要使用集合视图的路线。

当我调试时,它告诉我setter中的值已经是重复的,但我不知道为什么或在哪里重复。它只在我添加或删除记录时发生。任何帮助都很感激,谢谢!

如果在Refresh完成之前再次调用它,则可能发生该症状。具体来说,如果在await PlayerService.GetPlayersAsync()期间再次调用它,则Players上的动作顺序将为:

  • 调用# 1:Players.Clear ();
  • 调用# 2:Players.Clear ();
  • 呼叫#1:添加所有(现有)玩家
  • 呼叫2:添加所有玩家(包括新玩家)。

要检查是否发生这种情况,请添加检查:

async Task Refresh()
{
if (IsRefreshing)
throw new InvalidProgramException("Nested call to Refresh");
IsRefreshing = true;
try
{
... your existing code ...
}
finally
{
IsRefreshing = false;
}
}

会抛出异常吗?


如果是,则在代码后面移动Players.Clear()。这个可能会修复它:

var playersList = await PlayerService.GetPlayersAsync();
Players.Clear();

如果这不能解决这个问题,那么在触摸播放器的语句周围添加一个lock,以确保一个调用不能触摸它,直到前一个调用完成:

...
lock (PlayersLock)
{
Players.Clear();
foreach (Player player in playersList)
Players.Add(player);
}
...
// OUTSIDE of the method, define a member to act as a lock:
private object PlayersLock = new object();

相关内容