Search code examples
c#mvvmwinui-3windows-community-toolkit

Update a list (ObservableCollection) after instantiating it in the class constructor (MVVM e WINUI)


I am faced with this situation and I don't know if I'm doing it right. I have a classic ViewModel that I use for CRUD operations. Is it a good practice to instantiate the collection each time with "new" in order to apply filters? (see the Task DoSearch at the end). I wondered about this because I don't know what it means to generate new instances every time. Also because I had thought of creating the new record directly on the Grid by creating a record and filtering it to allow it to be edited and saved.

public class BlankViewModel : ObservableObject
{


    private readonly PersonDataContext _context;
    private Person? _selectedPerson;


    public BlankViewModel(PersonDataContext context)
    {
        _context = context;

        _context.Database.EnsureCreated();

        if (!_context.People.Any())
        {
            _context.People.AddRange(new Person[]
            {
                        new Person{ Id=1, Cognome="A", Nome="B" },
                        new Person{ Id=2, Cognome="C", Nome="D" },
                        new Person{ Id=3, Cognome="E", Nome="F" }
            });
            _context.SaveChanges();
        }

        people = new ObservableCollection<Person>(_context.People);

        AddCommand = new AsyncRelayCommand(DoAdd);
        RemoveCommand = new AsyncRelayCommand(DoRemove, () => SelectedPerson != null);
        SaveCommand = new AsyncRelayCommand(DoSave);
        SearchCommand = new AsyncRelayCommand<string>(DoSearch!);
    }


    private ObservableCollection<Person> people;

    public ObservableCollection<Person> People
    {
        get => people;
        set => SetProperty(ref people, value);
    }


    public Person SelectedPerson
    {
        get => _selectedPerson!;
        set
        {
            SetProperty(ref _selectedPerson, value);
            RemoveCommand.NotifyCanExecuteChanged();
        }
    }

    public IRelayCommand AddCommand
    {
        get;
    }
    public IRelayCommand RemoveCommand
    {
        get;
    }
    public IRelayCommand SaveCommand
    {
        get;
    }
    public IRelayCommand<string> SearchCommand
    {
        get;
    }


    private async Task DoAdd()
    {
        await Task.Delay(1000);

        //to DO ??


    }

    private async Task DoRemove()
    {
        if (SelectedPerson != null)
        {
            people.Remove(SelectedPerson);
            OnPropertyChanged(nameof(People));

            _context.People.Remove(SelectedPerson);
            await _context.SaveChangesAsync();

            SelectedPerson = null!;
        }
    }

    private async Task DoSave()
    {
        await _context.SaveChangesAsync();
    }

    private async Task DoSearch(string textToSearch)
    {
        people = new ObservableCollection<Person>(await _context.People.Where(x => x.Cognome.Contains(textToSearch)).ToListAsync());
        OnPropertyChanged(nameof(People));
    }
}

Solution

  • Is it a good practice to instantiate the collection each time with "new" in order to apply filters? (see the Task DoSearch at the end)

    I'd just create a new List for the UI using each instance in the source data.

    Here's one way to do this.

    Note: Just to show you different options,

    • This sample is implemented in different ways to use the CommunityToolkit.
    • This sample uses an interface IPersonService instead of passing the context directly.

    PersonService.cs

    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace WinUI3Tests;
    
    public record Person(int Id, string Cognome, string Nome);
    
    public interface IPersonService
    {
        Task<IReadOnlyList<Person>> GetPeople();
    
        Task<bool> Remove(Person person);
    }
    
    public class FakePersonService : IPersonService
    {
        private readonly List<Person> people = new()
        {
            new Person(Id: 1, Cognome: "A", Nome: "B"),
            new Person(Id: 2, Cognome: "C", Nome: "D"),
            new Person(Id: 3, Cognome: "E", Nome: "F"),
        };
    
        public Task<IReadOnlyList<Person>> GetPeople()
        {
            return Task.FromResult<IReadOnlyList<Person>>(this.people);
        }
    
        public async Task AddRange(IEnumerable<Person> people)
        {
            await Task.CompletedTask;
            this.people.AddRange(people);
        }
    
        public Task SaveChanges()
        {
            return Task.CompletedTask;
        }
    
        public Task<bool> Remove(Person person)
        {
            return Task.FromResult(this.people.Remove(person));
        }
    }
    

    BlankViewModel.cs

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.Linq;
    
    namespace WinUI3Tests;
    
    public partial class BlankViewModel : ObservableObject
    {
        private IPersonService? personService;
    
        public async Task SetPersonService(IPersonService personService)
        {
            this.personService = personService;
            People = await this.personService.GetPeople();
        }
    
        [ObservableProperty]
        private IEnumerable<Person>? people;
    
        [ObservableProperty]
        private Person? selectedPerson;
    
        [RelayCommand]
        private async Task Add()
        {
            await Task.Delay(1000);
            // to DO ??
        }
    
        [RelayCommand]
        private async Task Remove()
        {
            if (this.personService is not null &&
                SelectedPerson != null)
            {
                _ = this.personService.Remove(SelectedPerson);
                People = await this.personService.GetPeople();
                SelectedPerson = null!;
            }
        }
    
        [RelayCommand]
        private async Task Save()
        {
            await Task.CompletedTask;
        }
    
        [RelayCommand]
        private async Task Search(string textToSearch)
        {
            if (this.personService is not null)
            {
                People = (await this.personService.GetPeople())
                    .Where(x => x.Cognome.Contains(textToSearch));
            }
        }
    }
    

    MainPage.xaml.cs

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    
    namespace WinUI3Tests;
    
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.Loaded += MainPage_Loaded;
        }
    
        private async void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            await ViewModel.SetPersonService(new FakePersonService());
        }
    
        public BlankViewModel ViewModel { get; } = new();
    }
    

    MainPage.xaml

    <Page
        x:Class="WinUI3Tests.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:WinUI3Tests"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
    
        <Grid RowDefinitions="Auto,*">
            <StackPanel
                Grid.Row="0"
                Orientation="Horizontal">
                <TextBox x:Name="TextToSearchTextBox" />
                <Button
                    Command="{x:Bind ViewModel.AddCommand}"
                    Content="Add" />
                <Button
                    Command="{x:Bind ViewModel.RemoveCommand}"
                    Content="Remove" />
                <Button
                    Command="{x:Bind ViewModel.SearchCommand}"
                    CommandParameter="{x:Bind TextToSearchTextBox.Text, Mode=OneWay}"
                    Content="Search" />
            </StackPanel>
    
            <ListView
                Grid.Row="1"
                ItemsSource="{x:Bind ViewModel.People, Mode=OneWay}"
                SelectedItem="{x:Bind ViewModel.SelectedPerson, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </Grid>
    
    </Page>