Condividi tramite


Implementare MVVM con MVVM Toolkit

Ora che è disponibile la struttura del progetto, è possibile iniziare a implementare il modello MVVM usando MVVM Toolkit. Questo passaggio implica la creazione di ViewModels che sfruttano le funzionalità di MVVM Toolkit, ad esempio ObservableObject per la notifica delle modifiche alle proprietà e RelayCommand per l'implementazione del comando.

Installare il pacchetto NuGet MVVM Toolkit

È necessario installare MVVM Toolkit nei progetti WinUINotes e WinUINotes.Bus .

Uso di Visual Studio

  1. Fare clic con il pulsante destro del mouse sul progetto WinUINotes.Bus in Esplora soluzioni.
  2. Selezionare Gestisci pacchetti NuGet.
  3. Cercare CommunityToolkit.Mvvm e installare la versione stabile più recente.
  4. Ripetere questi passaggi per il progetto WinUINotes .

Uso del .NET CLI

In alternativa, è possibile usare l'interfaccia della riga di comando di .NET per installare il pacchetto:

dotnet add WinUINotes.Bus package CommunityToolkit.Mvvm
dotnet add WinUINotes package CommunityToolkit.Mvvm

Decisioni di progettazione per il livello del modello

Quando si implementa MVVM, è importante decidere come strutturare le classi del modello in relazione a ViewModels. In questa esercitazione le classi di modello (Note e AllNotes) sono responsabili della rappresentazione dei dati, della logica di business e dell'aggiornamento dell'archiviazione dei dati. ViewModels gestisce le proprietà osservabili, la notifica delle modifiche e i comandi per l'interazione dell'interfaccia utente.

In un'implementazione più semplice, è possibile usare oggetti CLR (POCO) semplici per le classi di modello senza metodi di accesso ai dati o logica di business. In tal caso, i ViewModel gestiscono tutte le operazioni sui dati tramite il livello di servizio. Tuttavia, per questa esercitazione, le classi di modelli includono metodi per il caricamento, il salvataggio e l'eliminazione di note per fornire una separazione più chiara delle problematiche e mantenere i ViewModel incentrati sulla logica di presentazione.

Spostare il modello Note

Spostare la Note classe nel progetto WinUINotes.Bus . Rimane una classe modello semplice con una certa logica per la rappresentazione dei dati e la gestione dello stato, ma senza funzionalità MVVM Toolkit. ViewModels gestisce le proprietà osservabili e la notifica delle modifiche, non il modello stesso.

  1. Nel progetto WinUINotes.Bus creare una nuova cartella denominata Models.

  2. Spostare il Note.cs file dal progetto WinUINotes alla cartella WinUINotes.Bus/Models .

  3. Aggiorna il namespace affinché corrisponda al nuovo percorso.

    namespace WinUINotes.Models
    {
        public class Note
        {
            // Existing code remains unchanged
            ...
        }
    }
    

La Note classe è un modello di dati semplice. Non è necessaria una notifica di modifica perché ViewModels gestisce le proprietà osservabili e invia una notifica all'interfaccia utente delle modifiche.

Spostare il modello AllNotes

Spostare la AllNotes classe nel progetto WinUINotes.Bus .

  1. Spostare il AllNotes.cs file dal progetto WinUINotes alla cartella WinUINotes.Bus/Models .

  2. Aggiornare lo spazio dei nomi in modo che corrisponda al nuovo percorso:

    namespace WinUINotes.Models
    {
        public class AllNotes
        {
            // Existing code remains unchanged
            ...
        }
    }
    

Analogamente alla Note classe , AllNotes è una classe modello semplice. ViewModel gestisce il comportamento osservabile e gestisce la raccolta di note.

Creare il AllNotesViewModel

  1. Nel progetto WinUINotes.Bus creare una nuova cartella denominata ViewModels.

  2. Aggiungere un nuovo file di classe denominato AllNotesViewModel.cs con il contenuto seguente:

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Threading.Tasks;
    using WinUINotes.Models;
    
    namespace WinUINotes.ViewModels
    {
        public partial class AllNotesViewModel : ObservableObject
        {
            private readonly AllNotes allNotes;
    
            [ObservableProperty]
            private ObservableCollection<Note> notes;
    
            public AllNotesViewModel()
            {
                allNotes = new AllNotes();
                notes = new ObservableCollection<Note>();
            }
    
            [RelayCommand]
            public async Task LoadAsync()
            {
                await allNotes.LoadNotes();
                Notes.Clear();
                foreach (var note in allNotes.Notes)
                {
                    Notes.Add(note);
                }
            }
        }
    }
    

AllNotesViewModel Gestisce la raccolta di note visualizzate nell'interfaccia utente:

  • [ObservableProperty]: il notes campo genera automaticamente una proprietà pubblica Notes con notifica di modifica. Quando la Notes raccolta cambia, l'interfaccia utente viene aggiornata automaticamente.
  • allNotes model: questo campo privato contiene un'istanza del AllNotes modello, che gestisce le operazioni effettive sui dati.
  • [RelayCommand]: questo attributo genera una LoadCommand proprietà dal LoadAsync() metodo , consentendo all'interfaccia utente di attivare l'operazione di caricamento tramite il data binding.
  • LoadAsync() metodo: questo metodo carica le note dal modello, cancella la raccolta osservabile corrente e la popola con le note caricate. Questo modello garantisce che la raccolta associata all'interfaccia utente rimanga sincronizzata con i dati sottostanti.

La separazione tra il allNotes modello (operazioni sui dati) e la Notes raccolta osservabile (associazione dell'interfaccia utente) è un modello MVVM chiave che mantiene i problemi separati e la visualizzazione sincronizzata con i dati di ViewModel.

Per altre informazioni, vedere la documentazione:

Creare NoteViewModel

  1. Nella cartella ViewModels aggiungere un nuovo file di classe denominato NoteViewModel.cs:

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System;
    using System.Threading.Tasks;
    using WinUINotes.Models;
    
    namespace WinUINotes.ViewModels
    {
        public partial class NoteViewModel : ObservableObject
        {
            private Note note;
    
            [ObservableProperty]
            [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
            [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
            private string filename = string.Empty;
    
            [ObservableProperty]
            [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
            private string text = string.Empty;
    
            [ObservableProperty]
            private DateTime date = DateTime.Now;
    
            public NoteViewModel()
            {
                this.note = new Note();
                this.Filename = note.Filename;
            }
    
            public void InitializeForExistingNote(Note note)
            {
                this.note = note;
                this.Filename = note.Filename;
                this.Text = note.Text;
                this.Date = note.Date;
            }
    
            [RelayCommand(CanExecute = nameof(CanSave))]
            private async Task Save()
            {
                note.Filename = this.Filename;
                note.Text = this.Text;
                note.Date = this.Date;
                await note.SaveAsync();
    
                // Check if the DeleteCommand can now execute
                // (it can if the file now exists)
                DeleteCommand.NotifyCanExecuteChanged();
            }
    
            private bool CanSave()
            {
                return note is not null
                    && !string.IsNullOrWhiteSpace(this.Text)
                    && !string.IsNullOrWhiteSpace(this.Filename);
            }
    
            [RelayCommand(CanExecute = nameof(CanDelete))]
            private async Task Delete()
            {
                await note.DeleteAsync();
                note = new Note();
            }
    
            private bool CanDelete()
            {
                // Note: This is to illustrate how commands can be
                // enabled or disabled.
                // In a real application, you shouldn't perform
                // file operations in your CanExecute logic.
                return note is not null
                    && !string.IsNullOrWhiteSpace(this.Filename)
                    && this.note.NoteFileExists();
            }
        }
    }
    

Illustra NoteViewModel diverse funzionalità principali di MVVM Toolkit:

  • [ObservableProperty]: i filenamecampi , texte date generano automaticamente proprietà pubbliche (Filename, Text, Date) con il supporto delle notifiche di modifica.
  • [NotifyCanExecuteChangedFor]: questo attributo garantisce che, quando Filename o Text cambia, i comandi associati rivalutano se possono essere eseguiti. Ad esempio, quando si digita testo, il pulsante Salva abilita o disabilita automaticamente in base alla logica di convalida.
  • [RelayCommand(CanExecute = nameof(CanSave))]: questo attributo genera una SaveCommand proprietà associata al metodo CanSave()di convalida . Il comando è abilitato solo quando entrambi Text e Filename hanno valori.
  • InitializeForExistingNote(): questo metodo carica i dati di una nota esistente nelle proprietà ViewModel, che quindi aggiornano l'interfaccia utente tramite il data binding.
  • Salva logica: il Save() metodo aggiorna il modello sottostante Note con i valori delle proprietà correnti e chiama SaveAsync() il modello. Dopo il salvataggio, notifica all'oggetto DeleteCommand che deve essere rivalutato (poiché un file è ora esistente e può essere eliminato).
  • Elimina logica: il Delete() metodo chiama DeleteAsync() il modello di nota e crea una nuova nota vuota.

Più avanti in questa esercitazione, si integra il servizio di file per gestire le operazioni effettive sui file e si utilizza la classe WeakReferenceMessenger del toolkit MVVM per notificare ad altre parti dell'app quando una nota viene eliminata, mantenendo comunque un accoppiamento lasco.

Per altre informazioni, vedere la documentazione:

Aggiornare le visualizzazioni per l'uso di ViewModels

È ora necessario aggiornare le pagine XAML da associare ai nuovi ViewModel.

Aggiornare la visualizzazione AllNotesPage

  1. In AllNotesPage.xaml aggiornare l'associazione ItemsSource di ItemsView per usare la proprietà del ViewModel Notes:

    <ItemsView ItemsSource="{x:Bind viewModel.Notes}"
    ...
    
  2. Aggiornare il AllNotesPage.xaml.cs file in modo che sia simile al seguente:

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using WinUINotes.ViewModels;
    
    namespace WinUINotes.Views
    {
        public sealed partial class AllNotesPage : Page
        {
            private AllNotesViewModel? viewModel;
    
            public AllNotesPage()
            {
                this.InitializeComponent();
                viewModel = new AllNotesViewModel();
            }
    
            private void NewNoteButton_Click(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(NotePage));
            }
    
            private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
            {
                Frame.Navigate(typeof(NotePage), args.InvokedItem);
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                if (viewModel is not null)
                {
                    await viewModel.LoadAsync();
                }
            }
        }
    }
    

In questo file code-behind il costruttore crea direttamente un'istanza di AllNotesViewModel . Il OnNavigatedTo() metodo chiama il LoadAsync() metodo in ViewModel quando si passa alla pagina. Questo metodo carica le note dalla risorsa di archiviazione e aggiorna la raccolta osservabile. Questo modello garantisce che i dati vengano sempre aggiornati quando l'utente passa alla pagina tutte le note.

Più avanti in questa esercitazione si esegue il refactoring di questo codice per usare l'inserimento delle dipendenze, che consente di inserire ViewModel nel costruttore di pagina anziché crearlo direttamente. Questo approccio migliora la testabilità e semplifica la gestione dei cicli di vita di ViewModel.

Aggiornare la visualizzazione NotePage

  1. Nella NotePage.xaml, aggiornare le associazioni TextBox per Text e Header per utilizzare le proprietà del ViewModel. Aggiornare i StackPanel pulsanti per l'associazione ai comandi anziché usare gli Click eventi:

    ...
    <TextBox x:Name="NoteEditor"
             Text="{x:Bind noteVm.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             AcceptsReturn="True"
             TextWrapping="Wrap"
             PlaceholderText="Enter your note"
             Header="{x:Bind noteVm.Date.ToString()}"
             ScrollViewer.VerticalScrollBarVisibility="Auto"
             MaxWidth="400"
             Grid.Column="1"/>
    
    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Right"
                Spacing="4"
                Grid.Row="1" Grid.Column="1">
        <Button Content="Save" Command="{x:Bind noteVm.SaveCommand}"/>
        <Button Content="Delete" Command="{x:Bind noteVm.DeleteCommand}"/>
    </StackPanel>
    ...
    

    È anche possibile impostare UpdateSourceTrigger sull'associazione TextBox.Text per assicurarsi che le modifiche vengano inviate al ViewModel mentre l'utente digita. Questa impostazione consente al Save pulsante di abilitare o disabilitare in tempo reale in base all'input.

  2. In NotePage.xaml.cs aggiornare il codice per usare NoteViewModel:

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using WinUINotes.Models;
    using WinUINotes.ViewModels;
    
    namespace WinUINotes.Views
    {
        public sealed partial class NotePage : Page
        {
            private NoteViewModel? noteVm;
    
            public NotePage()
            {
                this.InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
                noteVm = new NoteViewModel();
    
                if (e.Parameter is Note note && noteVm is not null)
                {
                    noteVm.InitializeForExistingNote(note);
                }
            }
        }
    }
    

    Gli Click eventi per Save e Delete vengono rimossi perché i pulsanti ora si associano direttamente ai comandi in ViewModel. NoteViewModel viene creata un'istanza nel metodo OnNavigatedTo(). Se viene passato un Note parametro, inizializza ViewModel con i dati delle note esistenti.

Per altre informazioni, vedere la documentazione: