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));
}
}
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,
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>