Aggiornare l'app con i concetti relativi a MVVM
Questa serie di esercitazioni è progettata per continuare l'esercitazione Creare un'app MAUI .NET, che ha creato un'app che prende nota. In questa parte della serie 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.
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 un code-behind limitato che non contiene logica di business. 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 esegue il marshalling degli aggiornamenti dell'associazione al 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
Nell'esercitazione precedente i tipi di modello funzionavano sia come modello (dati) che come modello di visualizzazione (preparazione dei dati), mappati direttamente 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 nel dispositivo in una raccolta. |
Pensando all'app stessa, l'app contiene solo una parte di dati usata 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 di About
modelli e AllNotes
. Rimuovere questi modelli dal progetto:
- Trovare il riquadro Esplora soluzioni di Visual Studio.
- Fare clic con il pulsante destro del mouse sul file Models\About.cs e scegliere Elimina. Premere OK per eliminare il file.
- 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 da indicare quando la nota è stata creata o aggiornata per l'ultimo aggiornamento.
Attualmente, il caricamento e il salvataggio del modello sono stati eseguiti tramite le visualizzazioni e, in alcuni casi, dagli altri tipi di modello appena rimossi. 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.
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Modelli\Note.cs.
Nell'editor di codice aggiungere i due metodi seguenti alla
Note
classe . Questi metodi sono basati sull'istanza e gestiscono rispettivamente il salvataggio o l'eliminazione della nota corrente da o verso il 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));
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 da gestire il caricamento può essere
static
membro, 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.
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 delle note: *.notes.txt. Ogni nome file viene passato alLoad
metodo, caricando una singola nota. Infine, la raccolta di note viene ordinata in base alla data di ogni nota e restituita al chiamante.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.
Nel riquadro Esplora soluzioni di Visual Studio fare clic con il pulsante destro del mouse sul progetto >Notes Manage NuGet Packages (Gestisci pacchetti NuGet).
Selezionare la scheda Sfoglia.
Cercare communitytoolkit mvvm e selezionare il
CommunityToolkit.Mvvm
pacchetto, che deve essere il primo risultato.Assicurarsi che sia selezionata almeno la versione 8. Questa esercitazione è stata scritta usando la versione 8.0.0.
Selezionare quindi Installa e accettare eventuali richieste visualizzate.
A questo punto è possibile iniziare ad aggiornare il progetto aggiungendo modelli di visualizzazione.
Disaccoppia con i modelli di visualizzazione
La relazione view-to-viewmodel si basa principalmente sul sistema di associazione fornito dall'interfaccia utente dell'app multipiattaforma .NET (.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 code-behind della 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 code-behind per il gestore eventi salva il testo della nota in un file e passa 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 non essere 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 modello di visualizzazione è l'associazione che rappresenta e interagisce con il modello di dati per conto della vista.
I modelli di visualizzazione vengono archiviati in una cartella ViewModels .
- Trovare il riquadro Esplora soluzioni di Visual Studio.
- Fare clic con il pulsante destro del mouse sul progetto Notes e scegliere Aggiungi>nuova cartella. Assegnare alla cartella il nome ViewModels.
- Fare clic con il pulsante destro del mouse sulla cartella> ViewModels Aggiungi>classe e denominarla AboutViewModel.cs.
- Ripetere il passaggio precedente e creare altri due modelli di visualizzazione:
- NoteViewModel.cs
- NotesViewModel.cs
La struttura del progetto avrà un aspetto simile all'immagine seguente:
Informazioni sul modello di visualizzazione e sulla visualizzazione Informazioni
La visualizzazione Informazioni visualizza alcuni dati sullo schermo e, facoltativamente, passa 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 backup.
Creare il modello di visualizzazione Informazioni su:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\AboutViewModel.cs.
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 punta ShowMoreInfoCommand
al ShowMoreInfo
metodo , che apre il Web browser a una pagina specifica. Altre informazioni sul sistema di comando sono disponibili nella sezione successiva.
Informazioni sulla visualizzazione
La visualizzazione Informazioni deve essere modificata leggermente per collegarla al modello di visualizzazione creato nella sezione precedente. Nel file Views\AboutPage.xaml applicare le modifiche seguenti:
- Aggiornare lo
xmlns:models
spazio dei nomi XML in e specificarexmlns:viewModels
come destinazione lo spazio deiNotes.ViewModels
nomi .NET. - Modificare la
ContentPage.BindingContext
proprietà in una nuova istanza delAbout
modello di visualizzazione. - Rimuovere il gestore eventi del
Clicked
pulsante e usare laCommand
proprietà .
Aggiornare la visualizzazione Informazioni su:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AboutPage.xaml.
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"> <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
ShowMoreInfoCommand
è associato alla proprietà nel modello di visualizzazione e, quando viene richiamato, esegue il codice nel ShowMoreInfo
metodo , che apre il Web browser a una pagina specifica.
Eseguire la pulizia di About code-behind
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:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AboutPage.xaml.cs.
Suggerimento
Potrebbe essere necessario espandere Views\AboutPage.xaml per visualizzare il file.
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:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NoteViewModel.cs.
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 laNote
visualizzazione. Si noti che loCommunityToolkit.Mvvm.ComponentModel
spazio dei nomi viene importato. Questo spazio dei nomi fornisce l'oggettoObservableObject
usato come classe base. Per altre informazioniObservableObject
, vedere il passaggio successivo. Viene importato anche loCommunityToolkit.Mvvm.Input
spazio dei nomi . 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.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à eIdentifier
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 restituire un valore.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 ilOnPropertyChanged
metodo .Il
OnPropertyChanged
metodo viene fornito dallaObservableObject
classe base. Questo metodo usa il nome del codice chiamante, in questo caso il nome della proprietà Text e genera l'eventoObservableObject.PropertyChanged
. Questo evento fornisce il nome della proprietà a tutti i sottoscrittori di eventi. 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 laText
proprietà cambia, viene generato l'evento e qualsiasi elemento dell'interfaccia utente associato allaText
proprietà riceve una notifica della modifica della proprietà.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; }
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 modello di visualizzazione con un nuovo modello di backup, ovvero una nota vuota o per creare un modello di visualizzazione 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.
Aggiungere i
Save
metodi eDelete
: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 spostamento, che indica quale azione è stata eseguita e l'identificatore univoco della nota.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 nelquery
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.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 dispositivoIl
RefreshProperties
metodo è un altro metodo helper per assicurarsi che tutti i sottoscrittori associati a questo oggetto vengano informati che leText
proprietà eDate
sono state modificate. Poiché il modello sottostante (il_note
campo) viene modificato quando la nota viene caricata durante la navigazione, leText
proprietà eDate
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, aggiornare la visualizzazione Note. Nel file Views\NotePage.xaml applicare le modifiche seguenti:
- Aggiungere lo
xmlns:viewModels
spazio dei nomi XML destinato allo spazio deiNotes.ViewModels
nomi .NET. - Aggiungere un oggetto
BindingContext
alla pagina. - Rimuovere i gestori eventi delete e save button
Clicked
e sostituirli con i comandi.
Aggiornare la visualizzazione Note:
- Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\NotePage.xaml per aprire l'editor XAML.
- 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">
<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 associazione, perché è stato 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
{Binding
la sintassi. La sintassi viene convalidata e avvisa l'utente di un valore non valido. Provare a modificare la sintassi di associazione per inSaveCommand
Save123Command
. Se si passa il puntatore del mouse sul testo, si noterà che viene visualizzata una descrizione comando che informa che Save123Command non viene trovato. Questa notifica non è considerata un errore perché le associazioni sono dinamiche, è davvero un piccolo avviso che può aiutare a notare quando è stata digitata la proprietà errata.Se saveCommand è stato modificato in un valore diverso, ripristinarlo ora.
Pulire il code-behind note
Ora che l'interazione con la vista è cambiata 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:
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.
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 è di associazione direttamente al modello, che è stata eliminata 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 la progettazione 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 lo spostamento a 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:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NotesViewModel.cs.
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 è vuoto
NotesViewModel
in cui si aggiungeranno proprietà e comandi per supportare laAllNotes
visualizzazione.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 oggettoObservableCollection
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.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 ilModels.Note.LoadAll
metodo per riempire l'insieme osservabile con note. IlLoadAll
metodo restituisce le note comeModels.Note
tipo, ma la raccolta osservabile è una raccolta diViewModels.NoteViewModel
tipi. Il codice usa l'estensioneSelect
Linq per creare istanze del modello di visualizzazione dai modelli di nota restituiti daLoadAll
.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 mentreSelectNoteAsync
lo fa. I comandi possono facoltativamente avere un singolo parametro fornito quando viene richiamato il comando. Per ilSelectNoteAsync
metodo , il parametro rappresenta la nota selezionata.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))); } }
Modello di visualizzazione Note creato nel passaggio precedente dell'esercitazione, usato lo spostamento 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 osaved
. Il valore della chiave è l'identificatore univoco della nota.Se la nota è stata eliminata, tale nota viene trovata nella
AllNotes
raccolta dall'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 AllNotes
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 destinato allo spazio deiNotes.ViewModels
nomi .NET. - Aggiungere un oggetto
BindingContext
alla pagina. - Rimuovere l'evento del pulsante della
Clicked
barra degli strumenti e usare laCommand
proprietà . - Modificare l'oggetto
CollectionView
per associarloItemSource
aAllNotes
. - Modificare per
CollectionView
usare i comandi per reagire quando l'elemento selezionato cambia.
Aggiornare la visualizzazione AllNotes:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AllNotesPage.xaml.
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"> <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 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> <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 SelectionChangedCommand
proprietà e SelectionChangedCommandParameter
. Nel codice XAML aggiornato, la SelectionChangedCommand
proprietà è associata al modello di SelectNoteCommand
visualizzazione, 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 l'associazione usata per :CollectionView
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding AllNotes}"
Margin="20"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectNoteCommand}"
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
La SelectionChangedCommandParameter
proprietà utilizza l'associazione Source={RelativeSource Self}
. Fa Self
riferimento all'oggetto corrente, ovvero .CollectionView
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.
Pulire il code-behind AllNotes
Ora che l'interazione con la vista è stata modificata 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:
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.
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.
Ottenere 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:
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su ViewModels\NotesViewModel.cs.
ApplyQueryAttributes
Nel metodo esaminare la logica per la chiave della stringa di query salvata.Quando non
matchedNote
null
è , la nota viene aggiornata. Utilizzare ilAllNotes.Move
metodo per spostare l'oggetto nell'indicematchedNote
0, ovvero la 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. IlAllNotes.IndexOf
metodo recupera l'indice della nota.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 ilAllNotes.Add
metodo inAllNotes.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 visualizzata la visualizzazione Allnotes, l'evento NavigatedTo
viene generato per la pagina. Questo evento è un luogo perfetto 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 sovraccaricare una soluzione per questo problema e usare semplicemente l'evento NavigatedTo
per cancellare l'elemento selezionato da CollectionView
.
Nel riquadro Esplora soluzioni di Visual Studio fare doppio clic su Views\AllNotesPage.xaml.
Nel codice XAML per aggiungere
<ContentPage>
l'eventoNavigatedTo
:<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"> <ContentPage.BindingContext> <viewModels:NotesViewModel /> </ContentPage.BindingContext>
È possibile aggiungere un gestore eventi predefinito facendo clic con il pulsante destro del mouse sul nome del metodo dell'evento,
ContentPage_NavigatedTo
e scegliendo Vai a definizione. Questa azione apre views \AllNotesPage.xaml.cs nell'editor di codice.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 dinotesCollection
. Questo codice usa tale nome per accedereCollectionView
a e impostare suSelectedItem
null
. L'elemento selezionato viene cancellato ogni volta che si passa alla pagina.
Eseguire ora 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 per questa esercitazione. Se si vuole scaricare una copia del progetto completato per confrontare il codice, scaricare questo progetto.
Congratulazioni!
L'app usa ora modelli MVVM.
Passaggi successivi
I collegamenti seguenti forniscono altre informazioni relative ad alcuni dei concetti appresi in questa esercitazione:
Hai un problema con questa sezione? In caso di problemi, fornisci feedback per contribuire al miglioramento della sezione.