Condividi tramite


Aggiornare l'app con i concetti relativi a MVVM

Questa esercitazione è progettata per continuare l'esercitazione Creare un'app MAUI .NET , che ha creato un'app per prendere nota. In questa esercitazione si apprenderà come:

  • Implementare il modello model-view-viewmodel (MVVM).
  • Usare uno stile aggiuntivo della stringa di query per passare i dati durante la navigazione.

È consigliabile seguire prima l'esercitazione Creare un'app MAUI .NET , perché il codice creato in questa esercitazione è la base per questa esercitazione. Se il codice è stato perso o si vuole iniziare subito, scaricare il progetto.

Informazioni su MVVM

L'esperienza per gli sviluppatori .NET MAUI comporta in genere la creazione di un'interfaccia utente in XAML e l'aggiunta di code-behind che opera sull'interfaccia utente. I problemi di manutenzione complessi possono verificarsi man mano che le app vengono modificate e aumentano in dimensioni e ambito. Questi problemi includono l'accoppiamento stretto tra i controlli dell'interfaccia utente e la logica di business, che aumenta il costo di apportare modifiche all'interfaccia utente e la difficoltà di unit test di questo codice.

Il modello model-view-viewmodel (MVVM) consente di separare in modo pulito la logica di business e presentazione di un'app dalla relativa interfaccia utente. La gestione di una separazione netta tra la logica dell'app e l'interfaccia utente consente di risolvere numerosi problemi di sviluppo e semplifica il test, la gestione e l'evoluzione di un'app. Può anche migliorare significativamente le opportunità di riutilizzo del codice e consente agli sviluppatori e ai progettisti dell'interfaccia utente di collaborare più facilmente durante lo sviluppo delle rispettive parti di un'app.

Modello

Esistono tre componenti principali nel modello MVVM: il modello, la visualizzazione e il modello di visualizzazione. Ognuno ha uno scopo distinto. Il diagramma seguente illustra le relazioni tra i tre componenti.

Diagramma che illustra le parti di un'applicazione modellata MVVM

Oltre a comprendere le responsabilità di ogni componente, è anche importante comprendere come interagiscono. A livello generale, la vista "conosce" il modello di visualizzazione e il modello di visualizzazione "conosce" il modello, ma il modello non è a conoscenza del modello di visualizzazione e il modello di visualizzazione non è a conoscenza della visualizzazione. Di conseguenza, il modello di visualizzazione isola la visualizzazione dal modello e consente al modello di evolversi indipendentemente dalla visualizzazione.

La chiave per usare MVVM in modo efficace consiste nel comprendere come suddividere il codice dell'app nelle classi corrette e come le classi interagiscono tra loro.

Visualizza

La visualizzazione è responsabile della definizione della struttura, del layout e dell'aspetto di ciò che l'utente vede sullo schermo. Idealmente, ogni visualizzazione è definita in XAML, con una parte di codice limitata che non contiene logica aziendale. In alcuni casi, tuttavia, il code-behind potrebbe contenere la logica dell'interfaccia utente che implementa un comportamento visivo difficile da esprimere in XAML, ad esempio nelle animazioni.

Modello di visualizzazione

Il modello di visualizzazione implementa proprietà e comandi a cui la visualizzazione può associare i dati e notifica la visualizzazione di eventuali modifiche dello stato tramite eventi di notifica delle modifiche. Le proprietà e i comandi forniti dal modello di visualizzazione definiscono la funzionalità da offrire all'interfaccia utente, ma la visualizzazione determina la modalità di visualizzazione.

Il modello di visualizzazione è anche responsabile del coordinamento delle interazioni della vista con tutte le classi di modello necessarie. In genere esiste una relazione uno-a-molti tra il modello di visualizzazione e le classi del modello.

Ogni modello di visualizzazione fornisce dati da un modello in un modulo che la visualizzazione può utilizzare facilmente. A tale scopo, il modello di visualizzazione a volte esegue la conversione dei dati. L'inserimento di questa conversione dei dati nel modello di visualizzazione è una buona idea perché fornisce proprietà a cui è possibile associare la visualizzazione. Ad esempio, il modello di visualizzazione può combinare i valori di due proprietà per semplificare la visualizzazione da parte della visualizzazione.

Importante

.NET MAUI coordina gli aggiornamenti del binding sul thread dell'interfaccia utente. Quando si usa MVVM, questo consente di aggiornare le proprietà del modello di visualizzazione associato a dati da qualsiasi thread, con il motore di associazione di .NET MAUI che apporta gli aggiornamenti al thread dell'interfaccia utente.

Modello

Le classi del modello sono classi non visive che incapsulano i dati dell'app. Di conseguenza, si può pensare che il modello rappresenti il modello di dominio dell'app, che di solito include un modello di dati insieme alla logica di business e di convalida.

Aggiornare il modello

In questa prima parte dell'esercitazione si implementerà il modello model-view-viewmodel (MVVM). Per iniziare, aprire la soluzione Notes.sln in Visual Studio.

Pulire il modello

Nel tutorial precedente, i tipi di modello funzionavano sia come modello (dati) che come modello di visualizzazione (preparazione dei dati), che erano direttamente collegati a una vista. La tabella seguente descrive il modello:

File di codice Descrizione
Modelli/About.cs Modello About . Contiene campi di sola lettura che descrivono l'app stessa, ad esempio il titolo e la versione dell'app.
Modelli/Note.cs Modello Note . Rappresenta una nota.
Modelli/AllNotes.cs Modello AllNotes . Carica tutte le note sul dispositivo in una raccolta.

Pensando all'app stessa, c'è solo un dato utilizzato dall'app, ovvero Note. Le note vengono caricate dal dispositivo, salvate nel dispositivo e modificate tramite l'interfaccia utente dell'app. Non c'è davvero bisogno dei modelli About e AllNotes. Rimuovere questi modelli dal progetto:

  1. Trovare il riquadro Esplora soluzioni di Visual Studio.
  2. Fare clic con il pulsante destro del mouse sul file Models\About.cs e scegliere Elimina. Premere OK per eliminare il file.
  3. Fare clic con il pulsante destro del mouse sul file Models\AllNotes.cs e scegliere Elimina. Premere OK per eliminare il file.

L'unico file di modello rimanente è il file Models\Note.cs .

Aggiornare il modello

Il Note modello contiene:

  • Identificatore univoco, ovvero il nome file della nota archiviato nel dispositivo.
  • Testo della nota.
  • Data che indica quando la nota è stata creata o aggiornata.

Attualmente, il caricamento e il salvataggio del modello vengono eseguiti tramite le visualizzazioni; in alcuni casi, anche da altre tipologie di modelli appena rimosse. Il codice disponibile per il Note tipo deve essere il seguente:

namespace Notes.Models;

internal class Note
{
    public string Filename { get; set; }
    public string Text { get; set; }
    public DateTime Date { get; set; }
}

Il Note modello verrà espanso per gestire il caricamento, il salvataggio e l'eliminazione di note.

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Modelli\Note.cs.

  2. Nell'editor di codice aggiungere i due metodi seguenti alla Note classe . Questi metodi sono a base di istanza e gestiscono, rispettivamente, il salvataggio o l'eliminazione della nota corrente verso o dal dispositivo:

    public void Save() =>
    File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
    
    public void Delete() =>
        File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
    
  3. L'app deve caricare le note in due modi, caricando una singola nota da un file e caricando tutte le note nel dispositivo. Il codice per gestire il caricamento può essere static membri, senza richiedere l'esecuzione di un'istanza di classe.

    Aggiungere il codice seguente alla classe per caricare una nota in base al nome file:

    public static Note Load(string filename)
    {
        filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
    
        if (!File.Exists(filename))
            throw new FileNotFoundException("Unable to find file on local storage.", filename);
    
        return
            new()
            {
                Filename = Path.GetFileName(filename),
                Text = File.ReadAllText(filename),
                Date = File.GetLastWriteTime(filename)
            };
    }
    

    Questo codice accetta il nome del file come parametro, compila il percorso in cui le note vengono archiviate nel dispositivo e tenta di caricare il file, se esistente.

  4. Il secondo modo per caricare le note consiste nell'enumerare tutte le note nel dispositivo e caricarle in una raccolta.

    Aggiungere il codice seguente alla classe :

    public static IEnumerable<Note> LoadAll()
    {
        // Get the folder where the notes are stored.
        string appDataPath = FileSystem.AppDataDirectory;
    
        // Use Linq extensions to load the *.notes.txt files.
        return Directory
    
                // Select the file names from the directory
                .EnumerateFiles(appDataPath, "*.notes.txt")
    
                // Each file name is used to load a note
                .Select(filename => Note.Load(Path.GetFileName(filename)))
    
                // With the final collection of notes, order them by date
                .OrderByDescending(note => note.Date);
    }
    

    Questo codice restituisce una raccolta enumerabile di tipi di Note modello recuperando i file nel dispositivo che corrispondono al modello di file note: *.notes.txt. Ogni nome file viene passato al metodo Load, caricando una singola nota. Infine, la raccolta di note viene ordinata in base alla data di ogni nota e restituita al chiamante.

  5. Infine, aggiungere un costruttore alla classe che imposta i valori predefiniti per le proprietà, incluso un nome di file casuale:

    public Note()
    {
        Filename = $"{Path.GetRandomFileName()}.notes.txt";
        Date = DateTime.Now;
        Text = "";
    }
    

Il Note codice della classe dovrebbe essere simile al seguente:

namespace Notes.Models;

internal class Note
{
    public string Filename { get; set; }
    public string Text { get; set; }
    public DateTime Date { get; set; }

    public Note()
    {
        Filename = $"{Path.GetRandomFileName()}.notes.txt";
        Date = DateTime.Now;
        Text = "";
    }

    public void Save() =>
    File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);

    public void Delete() =>
        File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));

    public static Note Load(string filename)
    {
        filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);

        if (!File.Exists(filename))
            throw new FileNotFoundException("Unable to find file on local storage.", filename);

        return
            new()
            {
                Filename = Path.GetFileName(filename),
                Text = File.ReadAllText(filename),
                Date = File.GetLastWriteTime(filename)
            };
    }

    public static IEnumerable<Note> LoadAll()
    {
        // Get the folder where the notes are stored.
        string appDataPath = FileSystem.AppDataDirectory;

        // Use Linq extensions to load the *.notes.txt files.
        return Directory

                // Select the file names from the directory
                .EnumerateFiles(appDataPath, "*.notes.txt")

                // Each file name is used to load a note
                .Select(filename => Note.Load(Path.GetFileName(filename)))

                // With the final collection of notes, order them by date
                .OrderByDescending(note => note.Date);
    }
}

Ora che il Note modello è completo, è possibile creare i modelli di visualizzazione.

Creare il modello di visualizzazione Informazioni su

Prima di aggiungere modelli di visualizzazione al progetto, aggiungere un riferimento a MVVM Community Toolkit. Questa libreria è disponibile in NuGet e fornisce tipi e sistemi che consentono di implementare il modello MVVM.

  1. Nel riquadro Esplora soluzioni di Visual Studio fare clic con il pulsante destro del mouse sul progetto >Gestisci pacchetti NuGet.

  2. Selezionare la scheda Sfoglia.

  3. Cercare communitytoolkit mvvm e selezionare il CommunityToolkit.Mvvm pacchetto, che deve essere il primo risultato.

  4. Assicurarsi che sia selezionata almeno la versione 8. Questa esercitazione è stata scritta usando la versione 8.0.0.

  5. Selezionare quindi Installa e accettare eventuali richieste visualizzate.

    Ricerca del pacchetto CommunityToolkit.Mvvm in NuGet.

A questo punto è possibile iniziare ad aggiornare il progetto aggiungendo modelli di visualizzazione.

Disaccoppia dai modelli di visualizzazione

La relazione view-to-viewmodel si basa principalmente sul sistema di binding fornito dall'interfaccia utente dell'app multipiattaforma .NET MAUI. L'app usa già l'associazione nelle visualizzazioni per visualizzare un elenco di note e per presentare il testo e la data di una singola nota. La logica dell'app è attualmente fornita dal codice dietro la visualizzazione ed è direttamente collegata alla visualizzazione. Ad esempio, quando un utente modifica una nota e preme il pulsante Salva , viene generato l'evento Clicked per il pulsante. Quindi, il codice associato al gestore eventi salva il testo della nota in un file e torna alla schermata precedente.

La logica dell'app nel code-behind di una visualizzazione può diventare un problema quando cambia la visualizzazione. Ad esempio, se il pulsante viene sostituito con un controllo di input diverso o il nome di un controllo viene modificato, i gestori eventi potrebbero diventare non validi. Indipendentemente dalla modalità di progettazione della visualizzazione, lo scopo della visualizzazione è richiamare una sorta di logica dell'app e presentare informazioni all'utente. Per questa app, il Save pulsante salva la nota e quindi torna alla schermata precedente.

Il modello di visualizzazione offre all'app una posizione specifica per inserire la logica dell'app indipendentemente dal modo in cui l'interfaccia utente è progettata o dal modo in cui i dati vengono caricati o salvati. Il viewmodel è il collante che rappresenta e interagisce con il modello di dati per conto della vista.

I modelli di visualizzazione vengono archiviati in una cartella ViewModels .

  1. Trovare il riquadro Esplora soluzioni di Visual Studio.
  2. Fare clic con il pulsante destro del mouse sul progetto Notes e scegliere Aggiungi>nuova cartella. Assegnare alla cartella il nome ViewModels.
  3. Fare clic con il pulsante destro del mouse sulla cartella >Aggiungi>classe e denominarla AboutViewModel.cs.
  4. Ripetere il passaggio precedente e creare altri due modelli di visualizzazione:
    • NoteViewModel.cs
    • NotesViewModel.cs

La struttura del progetto dovrebbe essere simile all'immagine seguente:

Esplora soluzioni che mostra le cartelle MVVM.

Informazioni sul modello di visualizzazione e sulla vista

La Vista informazioni visualizza alcuni dati sullo schermo e, facoltativamente, naviga a un sito Web con altre informazioni. Poiché questa visualizzazione non include dati da modificare, ad esempio con un controllo di immissione di testo o selezionando elementi da un elenco, è un buon candidato per dimostrare l'aggiunta di un modello di visualizzazione. Per il modello di visualizzazione 'Informazioni', non esiste un modello di supporto.

Creare il About ViewModel:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\AboutViewModel.cs.

  2. Incollare il codice seguente:

    using CommunityToolkit.Mvvm.Input;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class AboutViewModel
    {
        public string Title => AppInfo.Name;
        public string Version => AppInfo.VersionString;
        public string MoreInfoUrl => "https://aka.ms/maui";
        public string Message => "This app is written in XAML and C# with .NET MAUI.";
        public ICommand ShowMoreInfoCommand { get; }
    
        public AboutViewModel()
        {
            ShowMoreInfoCommand = new AsyncRelayCommand(ShowMoreInfo);
        }
    
        async Task ShowMoreInfo() =>
            await Launcher.Default.OpenAsync(MoreInfoUrl);
    }
    

Il frammento di codice precedente contiene alcune proprietà che rappresentano informazioni sull'app, ad esempio il nome e la versione. Questo frammento è esattamente lo stesso del modello About eliminato in precedenza. Tuttavia, questo modello di visualizzazione contiene un nuovo concetto, la proprietà del ShowMoreInfoCommand comando.

I comandi sono azioni associabili che richiamano il codice e sono un ottimo posto per inserire la logica dell'app. In questo esempio, il ShowMoreInfoCommand punta al metodo ShowMoreInfo, che apre il browser Web su una pagina specifica. Altre informazioni sul sistema di comando sono disponibili nella sezione successiva.

Informazioni sulla visualizzazione

La vista Informazioni deve essere modificata leggermente per connetterla al modello di vista creato nella sezione precedente. Nel file Views\AboutPage.xaml applicare le modifiche seguenti:

  • Aggiornare lo spazio dei nomi XML xmlns:models a xmlns:viewModels e indirizzare lo spazio dei nomi .NET Notes.ViewModels.
  • Modificare la ContentPage.BindingContext proprietà in una nuova istanza del About modello di visualizzazione.
  • Rimuovere il gestore eventi del Clicked pulsante e usare la Command proprietà .

Aggiorna la schermata Informazioni:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AboutPage.xaml.

  2. Incollare il codice seguente:

    <?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:Notes.ViewModels"
                 x:Class="Notes.Views.AboutPage"
                 x:DataType="viewModels:AboutViewModel">
        <ContentPage.BindingContext>
            <viewModels:AboutViewModel />
        </ContentPage.BindingContext>
        <VerticalStackLayout Spacing="10" Margin="10">
            <HorizontalStackLayout Spacing="10">
                <Image Source="dotnet_bot.png"
                       SemanticProperties.Description="The dot net bot waving hello!"
                       HeightRequest="64" />
                <Label FontSize="22" FontAttributes="Bold" Text="{Binding Title}" VerticalOptions="End" />
                <Label FontSize="22" Text="{Binding Version}" VerticalOptions="End" />
            </HorizontalStackLayout>
    
            <Label Text="{Binding Message}" />
            <Button Text="Learn more..." Command="{Binding ShowMoreInfoCommand}" />
        </VerticalStackLayout>
    
    </ContentPage>
    

    Il frammento di codice precedente evidenzia le righe modificate in questa versione della visualizzazione.

Si noti che il pulsante usa la Command proprietà . Molti controlli hanno una Command proprietà richiamata quando l'utente interagisce con il controllo . Quando viene usato con un pulsante, il comando viene richiamato quando un utente preme il pulsante, in modo simile al modo in cui viene richiamato il Clicked gestore eventi, ad eccezione del fatto che è possibile eseguire l'associazione Command a una proprietà nel modello di visualizzazione.

In questa visualizzazione, quando l'utente preme il pulsante, viene richiamato Command. L'oggetto Command è associato alla proprietà nel viewmodel ShowMoreInfoCommand e, quando viene richiamato, esegue il codice nel metodo ShowMoreInfo, che apre il browser web su una pagina specifica.

Eseguire la pulizia del code-behind di About

Il ShowMoreInfo pulsante non usa il gestore eventi, quindi il LearnMore_Clicked codice deve essere rimosso dal file Views\AboutPage.xaml.cs . Eliminare il codice, la classe deve contenere solo il costruttore:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Visualizzazioni\AboutPage.xaml.cs.

    Suggerimento

    Potrebbe essere necessario espandere Views\AboutPage.xaml per visualizzare il file.

  2. Sostituire il codice con il frammento di codice seguente:

    namespace Notes.Views;
    
    public partial class AboutPage : ContentPage
    {
        public AboutPage()
        {
            InitializeComponent();
        }
    }
    

Creare il modello di visualizzazione Note

L'obiettivo dell'aggiornamento della visualizzazione Note è spostare la maggior parte delle funzionalità possibile dal code-behind XAML e inserirla nel modello di visualizzazione Note.

Modello di visualizzazione note

In base alle esigenze della visualizzazione Note , il modello di visualizzazione Note deve fornire gli elementi seguenti:

  • Testo della nota.
  • Data/ora di creazione o ultimo aggiornamento della nota.
  • Comando che salva la nota.
  • Comando che elimina la nota.

Creare il modello di visualizzazione Note:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NoteViewModel.cs.

  2. Sostituire il codice in questo file con il frammento di codice seguente:

    using CommunityToolkit.Mvvm.Input;
    using CommunityToolkit.Mvvm.ComponentModel;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class NoteViewModel : ObservableObject, IQueryAttributable
    {
        private Models.Note _note;
    
    }
    

    Questo codice è il modello di visualizzazione vuoto Note in cui si aggiungeranno proprietà e comandi per supportare la Note visualizzazione. Nota che il CommunityToolkit.Mvvm.ComponentModel namespace viene importato. Questo spazio dei nomi fornisce l'oggetto ObservableObject usato come classe base. Scoprirai di più riguardo a ObservableObject nel passaggio successivo. Anche lo CommunityToolkit.Mvvm.Input spazio dei nomi viene importato. Questo spazio dei nomi fornisce alcuni tipi di comando che richiamano metodi in modo asincrono.

    Il Models.Note modello viene archiviato come campo privato. Le proprietà e i metodi di questa classe useranno questo campo.

  3. Aggiungere le proprietà seguenti alla classe :

    public string Text
    {
        get => _note.Text;
        set
        {
            if (_note.Text != value)
            {
                _note.Text = value;
                OnPropertyChanged();
            }
        }
    }
    
    public DateTime Date => _note.Date;
    
    public string Identifier => _note.Filename;
    

    Le Date proprietà e Identifier sono proprietà semplici che recuperano solo i valori corrispondenti dal modello.

    Suggerimento

    Per le proprietà, la sintassi => crea una proprietà get-only in cui l'istruzione a destra di => deve essere valutata per ottenere un valore da restituire.

    La Text proprietà controlla innanzitutto se il valore impostato è un valore diverso. Se il valore è diverso, tale valore viene passato alla proprietà del modello e viene chiamato il OnPropertyChanged metodo .

    Il OnPropertyChanged metodo viene fornito dalla ObservableObject classe base. Questo metodo usa il nome del codice chiamante, in questo caso il nome della proprietà Text e genera l'evento ObservableObject.PropertyChanged . Questo evento fornisce il nome della proprietà a tutti i sottoscrittori all'evento. Il sistema di associazione fornito da .NET MAUI riconosce questo evento e aggiorna eventuali associazioni correlate nell'interfaccia utente. Per il modello di visualizzazione Note, quando la Text proprietà cambia, viene generato l'evento e qualsiasi elemento dell'interfaccia utente associato alla Text proprietà riceve una notifica della modifica della proprietà.

  4. Aggiungere le proprietà di comando seguenti alla classe , ovvero i comandi a cui è possibile associare la vista:

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }
    
  5. Aggiungere i costruttori seguenti alla classe :

    public NoteViewModel()
    {
        _note = new Models.Note();
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }
    
    public NoteViewModel(Models.Note note)
    {
        _note = note;
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }
    

    Questi due costruttori vengono usati per creare il viewmodel con un nuovo modello di supporto, ovvero una nota vuota, o per creare un viewmodel che usa l'istanza del modello specificata.

    I costruttori configurano anche i comandi per il modello di visualizzazione. Aggiungere quindi il codice per questi comandi.

  6. Aggiungere il metodo Save e il metodo Delete.

    private async Task Save()
    {
        _note.Date = DateTime.Now;
        _note.Save();
        await Shell.Current.GoToAsync($"..?saved={_note.Filename}");
    }
    
    private async Task Delete()
    {
        _note.Delete();
        await Shell.Current.GoToAsync($"..?deleted={_note.Filename}");
    }
    

    Questi metodi vengono richiamati dai comandi associati. Eseguono le azioni correlate sul modello e fanno passare l'app alla pagina precedente. Un parametro della stringa di query viene aggiunto al .. percorso di navigazione, indicando quale azione è stata eseguita e qual è l'identificatore univoco della nota.

  7. Aggiungere quindi il ApplyQueryAttributes metodo alla classe , che soddisfa i requisiti dell'interfaccia IQueryAttributable :

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("load"))
        {
            _note = Models.Note.Load(query["load"].ToString());
            RefreshProperties();
        }
    }
    

    Quando una pagina o il contesto di associazione di una pagina implementa questa interfaccia, i parametri della stringa di query usati nella navigazione vengono passati al ApplyQueryAttributes metodo . Questo modello di visualizzazione viene usato come contesto di associazione per la visualizzazione Note. Quando si passa alla visualizzazione Note , il contesto di associazione della visualizzazione (questo modello di visualizzazione) viene passato ai parametri della stringa di query usati durante la navigazione.

    Questo codice controlla se la load chiave è stata specificata nel query dizionario. Se questa chiave viene trovata, il valore deve essere l'identificatore (il nome file) della nota da caricare. Questa nota viene caricata e impostata come oggetto modello sottostante di questa istanza del modello di visualizzazione.

  8. Infine, aggiungere questi due metodi helper alla classe :

    public void Reload()
    {
        _note = Models.Note.Load(_note.Filename);
        RefreshProperties();
    }
    
    private void RefreshProperties()
    {
        OnPropertyChanged(nameof(Text));
        OnPropertyChanged(nameof(Date));
    }
    

    Il Reload metodo è un metodo helper che aggiorna l'oggetto modello di backup, ricaricandolo dall'archiviazione del dispositivo

    Il RefreshProperties metodo è un altro metodo helper per assicurarsi che tutti i sottoscrittori associati a questo oggetto vengano informati che le Text proprietà e Date sono state modificate. Poiché il modello sottostante (il _note campo) viene modificato quando la nota viene caricata durante la navigazione, le Text proprietà e Date non vengono effettivamente impostate su nuovi valori. Poiché queste proprietà non sono impostate direttamente, le associazioni associate a tali proprietà non verranno notificate perché OnPropertyChanged non viene chiamato per ogni proprietà. RefreshProperties assicura che le associazioni a queste proprietà vengano aggiornate.

Il codice per la classe dovrebbe essere simile al frammento di codice seguente:

using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows.Input;

namespace Notes.ViewModels;

internal class NoteViewModel : ObservableObject, IQueryAttributable
{
    private Models.Note _note;

    public string Text
    {
        get => _note.Text;
        set
        {
            if (_note.Text != value)
            {
                _note.Text = value;
                OnPropertyChanged();
            }
        }
    }

    public DateTime Date => _note.Date;

    public string Identifier => _note.Filename;

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }

    public NoteViewModel()
    {
        _note = new Models.Note();
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }

    public NoteViewModel(Models.Note note)
    {
        _note = note;
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }

    private async Task Save()
    {
        _note.Date = DateTime.Now;
        _note.Save();
        await Shell.Current.GoToAsync($"..?saved={_note.Filename}");
    }

    private async Task Delete()
    {
        _note.Delete();
        await Shell.Current.GoToAsync($"..?deleted={_note.Filename}");
    }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("load"))
        {
            _note = Models.Note.Load(query["load"].ToString());
            RefreshProperties();
        }
    }

    public void Reload()
    {
        _note = Models.Note.Load(_note.Filename);
        RefreshProperties();
    }

    private void RefreshProperties()
    {
        OnPropertyChanged(nameof(Text));
        OnPropertyChanged(nameof(Date));
    }
}

Visualizzazione note

Ora che il modello di visualizzazione è stato creato, aggiorna la visualizzazione Note. Nel file Views\NotePage.xaml applicare le modifiche seguenti:

  • Aggiungere lo xmlns:viewModels spazio dei nomi XML che punta allo spazio dei nomi Notes.ViewModels .NET.
  • Aggiungere un oggetto BindingContext alla pagina.
  • Rimuovere i gestori eventi dei pulsanti "elimina" e "salva" Clicked e sostituirli con comandi.

Aggiornare la visualizzazione delle note:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\NotePage.xaml per aprire l'editor XAML.

  2. Incollare il codice seguente:

    <?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:Notes.ViewModels"
                 x:Class="Notes.Views.NotePage"
                 Title="Note"
                 x:DataType="viewModels:NoteViewModel">
        <ContentPage.BindingContext>
            <viewModels:NoteViewModel />
        </ContentPage.BindingContext>
        <VerticalStackLayout Spacing="10" Margin="5">
            <Editor x:Name="TextEditor"
                    Placeholder="Enter your note"
                    Text="{Binding Text}"
                    HeightRequest="100" />
    
            <Grid ColumnDefinitions="*,*" ColumnSpacing="4">
                <Button Text="Save"
                        Command="{Binding SaveCommand}"/>
    
                <Button Grid.Column="1"
                        Text="Delete"
                        Command="{Binding DeleteCommand}"/>
    
            </Grid>
        </VerticalStackLayout>
    </ContentPage>
    

In precedenza, questa visualizzazione non dichiarava un contesto di binding, poiché esso veniva fornito dal code-behind della pagina stessa. L'impostazione del contesto di associazione direttamente nel codice XAML offre due elementi:

  • In fase di esecuzione, quando si passa alla pagina, viene visualizzata una nota vuota. Questo perché viene richiamato il costruttore senza parametri per il contesto di associazione, ovvero il modello di visualizzazione. Se si ricorda correttamente, il costruttore senza parametri per il modello di visualizzazione Note crea una nota vuota.

  • IntelliSense nell'editor XAML mostra le proprietà disponibili non appena inizi a digitare la sintassi {Binding. La sintassi viene convalidata e avvisa l'utente di un valore non valido. Provare a modificare la sintassi di associazione da SaveCommand a Save123Command. Se passi il puntatore del mouse sul testo, noterai che viene visualizzato un tooltip che informa che Save123Command non è stato trovato. Questa notifica non è considerata un errore perché le associazioni sono dinamiche, è davvero un piccolo avviso che può aiutare a notare quando si digita la proprietà errata.

    Se hai modificato il SaveCommand in un valore diverso, ripristinalo ora.

Pulire il codice dietro le note

Ora che l'interazione con la vista è stata modificata dai gestori eventi ai comandi, aprire il file Views\NotePage.xaml.cs e sostituire tutto il codice con una classe che contiene solo il costruttore:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\NotePage.xaml.cs.

    Suggerimento

    Potrebbe essere necessario espandere Views\NotePage.xaml per visualizzare il file.

  2. Sostituire il codice con il frammento di codice seguente:

    namespace Notes.Views;
    
    public partial class NotePage : ContentPage
    {
        public NotePage()
        {
            InitializeComponent();
        }
    }
    

Creare il modello di visualizzazione Note

La coppia viewmodel-view finale è il modello di visualizzazione Note e la visualizzazione AllNotes. Attualmente, tuttavia, la vista è associata direttamente al modello, che è stato eliminato all'inizio di questa esercitazione. L'obiettivo di aggiornare la visualizzazione AllNotes consiste nello spostare la maggior parte delle funzionalità possibile dal code-behind XAML e inserirla nel modello di visualizzazione. Anche in questo caso, il vantaggio è che la visualizzazione può modificare il design con un effetto minimo sul codice.

Modello di visualizzazione Note

In base alla visualizzazione AllNotes che verrà visualizzata e alle interazioni che l'utente eseguirà, il modello di visualizzazione Note deve fornire gli elementi seguenti:

  • Raccolta di note.
  • Comando per gestire la navigazione verso una nota.
  • Comando per creare una nuova nota.
  • Aggiornare l'elenco delle note quando ne viene creata, eliminata o modificata una.

Creare il modello di visualizzazione Note:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NotesViewModel.cs.

  2. Sostituire il codice in questo file con il codice seguente:

    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class NotesViewModel: IQueryAttributable
    {
    }
    

    Questo codice è il blocco NotesViewModel vuoto in cui si aggiungeranno proprietà e comandi per supportare la AllNotes vista.

  3. Nel codice della NotesViewModel classe aggiungere le proprietà seguenti:

    public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; }
    public ICommand NewCommand { get; }
    public ICommand SelectNoteCommand { get; }
    

    La AllNotes proprietà è un oggetto ObservableCollection che archivia tutte le note caricate dal dispositivo. I due comandi verranno usati dalla visualizzazione per attivare le azioni di creazione di una nota o la selezione di una nota esistente.

  4. Aggiungere un costruttore senza parametri alla classe , che inizializza i comandi e carica le note dal modello:

    public NotesViewModel()
    {
        AllNotes = new ObservableCollection<ViewModels.NoteViewModel>(Models.Note.LoadAll().Select(n => new NoteViewModel(n)));
        NewCommand = new AsyncRelayCommand(NewNoteAsync);
        SelectNoteCommand = new AsyncRelayCommand<ViewModels.NoteViewModel>(SelectNoteAsync);
    }
    

    Si noti che l'insieme AllNotes usa il Models.Note.LoadAll metodo per riempire l'insieme osservabile con note. Il LoadAll metodo restituisce le note come Models.Note tipo, ma la raccolta osservabile è una raccolta di ViewModels.NoteViewModel tipi. Il codice usa l'estensione Select Linq per creare istanze del modello di visualizzazione dai modelli di nota restituiti da LoadAll.

  5. Creare i metodi di destinazione dei comandi:

    private async Task NewNoteAsync()
    {
        await Shell.Current.GoToAsync(nameof(Views.NotePage));
    }
    
    private async Task SelectNoteAsync(ViewModels.NoteViewModel note)
    {
        if (note != null)
            await Shell.Current.GoToAsync($"{nameof(Views.NotePage)}?load={note.Identifier}");
    }
    

    Si noti che il NewNoteAsync metodo non accetta un parametro mentre SelectNoteAsync lo fa. I comandi possono facoltativamente avere un singolo parametro fornito quando viene richiamato il comando. Per il SelectNoteAsync metodo , il parametro rappresenta la nota selezionata.

  6. Infine, implementare il IQueryAttributable.ApplyQueryAttributes metodo :

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("deleted"))
        {
            string noteId = query["deleted"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
            // If note exists, delete it
            if (matchedNote != null)
                AllNotes.Remove(matchedNote);
        }
        else if (query.ContainsKey("saved"))
        {
            string noteId = query["saved"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
            // If note is found, update it
            if (matchedNote != null)
                matchedNote.Reload();
    
            // If note isn't found, it's new; add it.
            else
                AllNotes.Add(new NoteViewModel(Note.Load(noteId)));
        }
    }
    

    Il modello di visualizzazione Note creato nel passaggio precedente dell'esercitazione ha utilizzato la navigazione quando la nota è stata salvata o eliminata. Il modello di visualizzazione è tornato alla visualizzazione AllNotes a cui è associato questo modello di visualizzazione. Questo codice rileva se la stringa di query contiene la deleted chiave o saved . Il valore della chiave è l'identificatore univoco della nota.

    Se la nota è stata eliminata, quella nota viene trovata nella raccolta AllNotes tramite l'identificatore specificato e rimossa.

    Esistono due possibili motivi per cui viene salvata una nota. La nota è stata appena creata o è stata modificata una nota esistente. Se la nota è già presente nella AllNotes raccolta, si tratta di una nota aggiornata. In questo caso, è sufficiente aggiornare l'istanza della nota nella raccolta. Se la nota non è presente nella raccolta, è una nuova nota e deve essere aggiunta alla raccolta.

Il codice per la classe dovrebbe essere simile al frammento di codice seguente:

using CommunityToolkit.Mvvm.Input;
using Notes.Models;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace Notes.ViewModels;

internal class NotesViewModel : IQueryAttributable
{
    public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; }
    public ICommand NewCommand { get; }
    public ICommand SelectNoteCommand { get; }

    public NotesViewModel()
    {
        AllNotes = new ObservableCollection<ViewModels.NoteViewModel>(Models.Note.LoadAll().Select(n => new NoteViewModel(n)));
        NewCommand = new AsyncRelayCommand(NewNoteAsync);
        SelectNoteCommand = new AsyncRelayCommand<ViewModels.NoteViewModel>(SelectNoteAsync);
    }

    private async Task NewNoteAsync()
    {
        await Shell.Current.GoToAsync(nameof(Views.NotePage));
    }

    private async Task SelectNoteAsync(ViewModels.NoteViewModel note)
    {
        if (note != null)
            await Shell.Current.GoToAsync($"{nameof(Views.NotePage)}?load={note.Identifier}");
    }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("deleted"))
        {
            string noteId = query["deleted"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

            // If note exists, delete it
            if (matchedNote != null)
                AllNotes.Remove(matchedNote);
        }
        else if (query.ContainsKey("saved"))
        {
            string noteId = query["saved"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

            // If note is found, update it
            if (matchedNote != null)
                matchedNote.Reload();

            // If note isn't found, it's new; add it.
            else
                AllNotes.Add(new NoteViewModel(Note.Load(noteId)));
        }
    }
}

Visualizzazione di tutte le note

Ora che il modello di visualizzazione è stato creato, aggiornare la visualizzazione AllNotes in modo che punti alle proprietà del modello di visualizzazione. Nel file Views\AllNotesPage.xaml applicare le modifiche seguenti:

  • Aggiungere lo xmlns:viewModels spazio dei nomi XML che punta allo spazio dei nomi Notes.ViewModels .NET.
  • Aggiungere un oggetto BindingContext alla pagina.
  • Rimuovere l'evento del pulsante della barra degli strumenti Clicked e usare la proprietà Command.
  • Modificare l'oggetto CollectionView per associarlo ItemSource a AllNotes.
  • Modifica CollectionView per utilizzare i comandi e reagire quando l'elemento selezionato cambia.

Aggiornare la visualizzazione AllNotes:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AllNotesPage.xaml.

  2. Incollare il codice seguente:

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AllNotesPage"
                 Title="Your Notes"
                 x:DataType="viewModels:NotesViewModel">
        <ContentPage.BindingContext>
            <viewModels:NotesViewModel />
        </ContentPage.BindingContext>
    
        <!-- Add an item to the toolbar -->
        <ContentPage.ToolbarItems>
            <ToolbarItem Text="Add" Command="{Binding NewCommand}" IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" />
        </ContentPage.ToolbarItems>
    
        <!-- Display notes in a list -->
        <CollectionView x:Name="notesCollection"
                        ItemsSource="{Binding AllNotes}"
                        Margin="20"
                        SelectionMode="Single"
                        SelectionChangedCommand="{Binding SelectNoteCommand}"
                        SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}">
            <!-- 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 x:DataType="viewModels:NoteViewModel">
                    <StackLayout>
                        <Label Text="{Binding Text}" FontSize="22"/>
                        <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
                    </StackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ContentPage>
    

La barra degli strumenti non usa più l'evento Clicked e usa invece un comando.

CollectionView supporta il comando con le proprietà SelectionChangedCommand e SelectionChangedCommandParameter. Nel codice XAML aggiornato, la SelectionChangedCommand proprietà è associata al modello di SelectNoteCommandvisualizzazione, il che significa che il comando viene richiamato quando l'elemento selezionato cambia. Quando il comando viene richiamato, il valore della SelectionChangedCommandParameter proprietà viene passato al comando .

Esaminare la rilegatura usata per CollectionView:

<CollectionView x:Name="notesCollection"
                ItemsSource="{Binding AllNotes}"
                Margin="20"
                SelectionMode="Single"
                SelectionChangedCommand="{Binding SelectNoteCommand}"
                SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}">

La SelectionChangedCommandParameter proprietà utilizza il binding Source={RelativeSource Self}. Il Self fa riferimento all'oggetto corrente, che è il CollectionView. Pertanto, x:DataType specifica CollectionView come tipo per l'associazione compilata. Si noti che il percorso di associazione è la SelectedItem proprietà . Quando il comando viene richiamato modificando l'elemento selezionato, viene richiamato il SelectNoteCommand comando e l'elemento selezionato viene passato al comando come parametro.

Affinché l'espressione di associazione definita nella SelectionChangedCommandParameter proprietà venga compilata, è necessario indicare al progetto di abilitare le associazioni compilate nelle espressioni che specificano la Source proprietà . A tale scopo, modificare il file di progetto per la soluzione e aggiungerlo <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> all'interno dell'elemento <PropertyGroup> :

<PropertyGroup>
  <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
</PropertyGroup>

Pulire il codice di backend di AllNotes

Ora che l'interazione con la vista è cambiata dai gestori eventi ai comandi, aprire il file Views\AllNotesPage.xaml.cs e sostituire tutto il codice con una classe che contiene solo il costruttore:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Visualizzazioni\AllNotesPage.xaml.cs.

    Suggerimento

    Potrebbe essere necessario espandere Views\AllNotesPage.xaml per visualizzare il file.

  2. Sostituire il codice con il frammento di codice seguente:

    namespace Notes.Views;
    
    public partial class AllNotesPage : ContentPage
    {
        public AllNotesPage()
        {
            InitializeComponent();
        }
    }
    

Eseguire l'app

È ora possibile eseguire l'app e tutto funziona. Esistono tuttavia due problemi con il comportamento dell'app:

  • Se si seleziona una nota, che apre l'editor, premere Salva e quindi provare a selezionare la stessa nota, non funziona.
  • Ogni volta che una nota viene modificata o aggiunta, l'elenco delle note non viene riordinato per visualizzare le note più recenti nella parte superiore.

Questi due problemi vengono risolti nel passaggio successivo dell'esercitazione.

Correggere il comportamento dell'app

Ora che il codice dell'app può essere compilato ed eseguito, probabilmente si noterà che ci sono due difetti con il comportamento dell'app. L'app non consente di selezionare nuovamente una nota già selezionata e l'elenco delle note non viene riordinato dopo la creazione o la modifica di una nota.

Mettere le note nella parte superiore dell'elenco

Prima di tutto, risolvere il problema di riordinamento con l'elenco delle note. Nel file ViewModels\NotesViewModel.cs la AllNotes raccolta contiene tutte le note da presentare all'utente. Sfortunatamente, lo svantaggio di usare un ObservableCollection è che deve essere ordinato manualmente. Per ottenere gli elementi nuovi o aggiornati all'inizio dell'elenco, seguire questa procedura:

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NotesViewModel.cs.

  2. Nel metodo ApplyQueryAttributes, esaminare la logica per la chiave della stringa di query salvato.

  3. Quando matchedNote non è null, la nota viene aggiornata. Utilizzare il metodo AllNotes.Move per spostare l'matchedNote all'indice 0, che corrisponde alla parte superiore dell'elenco.

    string noteId = query["saved"].ToString();
    NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
    // If note is found, update it
    if (matchedNote != null)
    {
        matchedNote.Reload();
        AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
    }
    

    Il AllNotes.Move metodo accetta due parametri per spostare la posizione di un oggetto nell'insieme. Il primo parametro è l'indice dell'oggetto da spostare e il secondo parametro è l'indice di dove spostare l'oggetto. Il AllNotes.IndexOf metodo recupera l'indice della nota.

  4. matchedNote Quando è null, la nota è nuova e viene aggiunta all'elenco. Invece di aggiungerlo, che aggiunge la nota alla fine dell'elenco, inserire la nota in corrispondenza dell'indice 0, che è la parte superiore dell'elenco. Modificare il AllNotes.Add metodo in AllNotes.Insert.

    string noteId = query["saved"].ToString();
    NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
    // If note is found, update it
    if (matchedNote != null)
    {
        matchedNote.Reload();
        AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
    }
    // If note isn't found, it's new; add it.
    else
        AllNotes.Insert(0, new NoteViewModel(Models.Note.Load(noteId)));
    

Il ApplyQueryAttributes metodo dovrebbe essere simile al frammento di codice seguente:

void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
{
    if (query.ContainsKey("deleted"))
    {
        string noteId = query["deleted"].ToString();
        NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

        // If note exists, delete it
        if (matchedNote != null)
            AllNotes.Remove(matchedNote);
    }
    else if (query.ContainsKey("saved"))
    {
        string noteId = query["saved"].ToString();
        NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

        // If note is found, update it
        if (matchedNote != null)
        {
            matchedNote.Reload();
            AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
        }
        // If note isn't found, it's new; add it.
        else
            AllNotes.Insert(0, new NoteViewModel(Models.Note.Load(noteId)));
    }
}

Consenti la selezione di una nota due volte

Nella visualizzazione AllNotes elenca CollectionView tutte le note, ma non consente di selezionare la stessa nota due volte. Esistono due modi in cui l'elemento rimane selezionato: quando l'utente modifica una nota esistente e quando l'utente passa forzatamente all'indietro. Il caso in cui l'utente salva una nota viene risolta con la modifica del codice nella sezione precedente che usa AllNotes.Move, quindi non è necessario preoccuparsi di questo caso.

Il problema da risolvere ora è correlato alla navigazione. Indipendentemente dal modo in cui viene raggiunta la visualizzazione Allnotes, l'evento NavigatedTo viene generato per la pagina. Questo evento è un'opportunità perfetta per deselezionare forzatamente l'elemento selezionato in CollectionView.

Tuttavia, con il modello MVVM applicato qui, il modello di visualizzazione non può attivare un elemento direttamente nella visualizzazione, ad esempio la cancellazione dell'elemento selezionato dopo il salvataggio della nota. Allora come fai a farlo? Una buona implementazione del modello MVVM riduce al minimo il code-behind nella visualizzazione. Esistono diversi modi per risolvere questo problema per supportare il modello di separazione MVVM. Tuttavia, è anche POSSIBILE inserire il codice nel code-behind della visualizzazione, soprattutto quando è direttamente collegato alla visualizzazione. MVVM offre molti grandi progetti e concetti che consentono di compartimentare l'app, migliorando la gestibilità e semplificando l'aggiunta di nuove funzionalità. Tuttavia, in alcuni casi, si potrebbe notare che MVVM incoraggia l'overengineering.

Non complicare eccessivamente una soluzione per questo problema e semplicemente utilizza l'evento NavigatedTo per cancellare l'elemento selezionato da CollectionView.

  1. Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AllNotesPage.xaml.

  2. Nel codice XAML, aggiungi l'evento <ContentPage> a NavigatedTo.

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AllNotesPage"
                 Title="Your Notes"
                 NavigatedTo="ContentPage_NavigatedTo"
                 x:DataType="viewModels:NotesViewModel">
        <ContentPage.BindingContext>
            <viewModels:NotesViewModel />
    
  3. È possibile aggiungere un gestore eventi predefinito facendo clic con il pulsante destro del mouse sul nome del metodo dell'evento, ContentPage_NavigatedToe scegliendo Vai a definizione. Questa azione apre l'oggetto Views\AllNotesPage.xaml.cs nell'editor di codice.

  4. Sostituire il codice del gestore eventi con il frammento di codice seguente:

    private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
    {
        notesCollection.SelectedItem = null;
    }
    

    Nel codice XAML, all'oggetto CollectionView è stato assegnato il nome di notesCollection. Questo codice usa tale nome per accedere a CollectionView e impostare SelectedItem su null. L'elemento selezionato viene cancellato ogni volta che si passa alla pagina.

Ora, esegui l'app. Provare a passare a una nota, premere il pulsante Indietro e selezionare la stessa nota una seconda volta. Il comportamento dell'app è corretto.

Esplorare il codice. Esplorare il codice per questa esercitazione. Se si vuole scaricare una copia del progetto completato per confrontare il codice, scaricare questo progetto.

L'app usa ora modelli MVVM.

Passaggi successivi

I collegamenti seguenti forniscono altre informazioni relative ad alcuni dei concetti appresi in questa esercitazione: