A community member has associated this post with a similar question:
MAUI CollectionView selected item error
Only moderators can edit this content.
MAUI CollectionView selected item not shown as selected
Hello.
I starts to learn MAUI Enterprise apps patterns and I found en error (or unexpected behavior) in CollectionView.
In my example, I have TrainingsViewModel whitch have ObservableCollection of TrainingViewModel property named as AllTrainings. This collection is binded in TrainingsView as CollectionView. Selected item of this CollectionView is binded as SelectedTraining in TrainingsViewModel.
Insert and Update of this Collection works fine, but when I remove an item, there is an UI error.
Binding to SelectedTraining works fine too.
But when I remove an item from Collection, I cannot select any item bellow by removed item position properly. In fact binding to selected item works after click, but UI doesn't show item as selected.
For example:
In pic. 1 I have 5 items in CollectionView and selected item no 5. UI shows properly selected item.
In pic. 2 I delete third item and then click to second item. UI shows selected item properly.
But when I click on 4. or 5. item after 3. item was delete (pic. 3), UI not shows selected item properly. It seems, that no item is selected. (But binding to selected items work well, when I click to edit, it works, only UI not show selected item).
I use MAUI version 8.0.
Minimal target is Android 5.0
Tested on Android 14.0, API 34.
Code snipped:
TrainingsViewModel.cs
public class TrainingsViewModel : IQueryAttributable
{
private readonly IDbService _dbService;
private readonly INavigationService _navigationService;
public ObservableCollection<ViewModels.TrainingViewModel> AllTrainings { get; }
public TrainingViewModel? SelectedTraining { get; set; }
public ICommand NewCommand { get; }
public ICommand LoadCommand { get; }
public ICommand EditCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand ImportCommand { get; }
public ICommand ExportCommand { get; }
public async void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.ContainsKey("deleted"))
{
int id;
string idString = query["deleted"].ToString();
if (int.TryParse(idString, out id))
{
TrainingViewModel training = AllTrainings.Where(x => x.Id == id).FirstOrDefault();
// If note exists, delete it
if (training != null)
{
// when I remove an item, it will already removed, but UI not works well.
AllTrainings.Remove(training);
// this works fine, but it not "fine" for me. For example can have lot of items.
//AllTrainings.Clear();
//LoadTrainings();
}
}
}
else if (query.ContainsKey("saved"))
{
int id;
string idString = query["saved"].ToString();
if (int.TryParse(idString, out id))
{
TrainingViewModel training = AllTrainings.Where(x => x.Id == id).FirstOrDefault();
// If note is found, update it
if (training != null)
{
training.Reload();
}
// If note isn't found, it's new; add it.
else
{
Training newTraining = await _dbService.GetTrainingAsync(id);
AllTrainings.Add(new TrainingViewModel(_dbService, _navigationService, newTraining));
}
}
}
}
public TrainingsViewModel(IDbService dbService, INavigationService navigationService)
{
_dbService = dbService;
_navigationService = navigationService;
AllTrainings = new ObservableCollection<TrainingViewModel>();
NewCommand = new AsyncRelayCommand(NewTrainingAsync);
EditCommand = new AsyncRelayCommand(EditTrainingAsync);
LoadTrainings();
}
private async void LoadTrainings()
{
var trainings = await _dbService.GetTrainingListAsync();
foreach (var training in trainings)
{
TrainingViewModel model = new TrainingViewModel(_dbService, _navigationService, training);
AllTrainings.Add(model);
}
}
private async Task NewTrainingAsync()
{
await _navigationService.NavigateToAsync("//TrainingAdd?new=x");
SelectedTraining = null;
}
private async Task EditTrainingAsync()
{
if (SelectedTraining != null)
{
await _navigationService.NavigateToAsync($"//TrainingEdit?load={SelectedTraining.Id}");
SelectedTraining = null;
}
}
}
TrainingViewModel.cs
public class TrainingViewModel : ObservableObject, IQueryAttributable
{
private Training _training;
private readonly IDbService _dbService;
private readonly INavigationService _navigationService;
public int Id
{
get => _training.Id;
set
{
if (_training.Id != value)
{
_training.Id = value;
OnPropertyChanged();
}
}
}
public string Name
{
get => _training.Name;
set
{
if (_training.Name != value)
{
_training.Name = value;
OnPropertyChanged();
}
}
}
public int TotalRoundCount
{
get => _training.TotalRoundCount;
set
{
if (_training.TotalRoundCount != value)
{
_training.TotalRoundCount = value;
OnPropertyChanged();
}
}
}
public ICommand AddCommand { get; private set; }
public ICommand SaveCommand { get; private set; }
public ICommand DeleteCommand { get; private set; }
public TrainingViewModel(IDbService dbService, INavigationService navigationService)
{
_training = new Models.Training();
_dbService = dbService;
AddCommand = new AsyncRelayCommand(AddAsync);
SaveCommand = new AsyncRelayCommand(SaveAsync);
DeleteCommand = new AsyncRelayCommand(DeleteAsync);
_navigationService = navigationService;
}
public TrainingViewModel(IDbService dbService, INavigationService navigationService, Models.Training training)
{
_training = training;
_dbService = dbService;
SaveCommand = new AsyncRelayCommand(SaveAsync);
DeleteCommand = new AsyncRelayCommand(DeleteAsync);
_navigationService = navigationService;
}
private async Task AddAsync()
{
var result = await _dbService.InsertTrainingAsync(_training);
}
private async Task SaveAsync()
{
_dbService.UpdateTrainingAsync(_training);
}
private async Task DeleteAsync()
{
_dbService.DeleteTrainingAsync(_training);
}
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.ContainsKey("load"))
{
int id;
if (int.TryParse(query["load"].ToString(), out id))
{
_training = await _dbService.GetTrainingAsync(id);
}
RefreshProperties();
}
else
{
_training = new Training();
RefreshProperties();
}
}
public async void Reload()
{
_training = await _dbService.GetTrainingAsync(_training.Id);
RefreshProperties();
}
private void RefreshProperties()
{
OnPropertyChanged(nameof(Id));
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(TotalRoundCount));
}
}
TrainingsView:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:IntervalStopwatch.ViewModels"
x:Class="IntervalStopwatch.Views.TrainingsView"
Title="TrainingsView">
<!-- Add an item to the toolbar -->
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding NewCommand}" IconImageSource="{FontImage Glyph='Add', Color=Black, Size=22}" />
<ToolbarItem Text="Edit" Command="{Binding EditCommand}" IconImageSource="{FontImage Glyph='Edit', Color=Black, Size=22}" />
</ContentPage.ToolbarItems>
<!-- Display notes in a list -->
<CollectionView x:Name="trainingsCollection"
ItemsSource="{Binding AllTrainings}"
Margin="20"
SelectionMode="Single"
SelectedItem="{Binding SelectedTraining}">
<!-- Designate how the collection of items are laid out -->
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
</CollectionView.ItemsLayout>
<!-- Define the appearance of each item in the list -->
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" FontSize="22"/>
<Label Text="{Binding TotalRoundCount}" FontSize="14" TextColor="Silver"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Can anyone tell me, what I am doing wrong, please?