Aktualisieren Ihrer App mit MVVM-Konzepten
Diese Tutorial-Reihe ist als Fortsetzung des Tutorials Eine .NET MAUI-App erstellen ausgelegt, in dem eine Notizen-App erstellt wurde. In diesem Teil der Reihe wird Folgendes vermittelt:
- Implementieren Sie das Model-View-Viewmodel (MVVM)-Muster.
- Verwenden Sie eine zusätzliche Art von Abfragezeichenfolge für die Übergabe von Daten während der Navigation.
Wir empfehlen Ihnen dringend, zuerst das Tutorial Eine .NET MAUI-App erstellen zu lesen, da der in diesem Tutorial erstellte Code die Grundlage für dieses Tutorial bildet. Wenn Sie den Code verloren haben oder neu anfangen wollen, laden Sie dieses Projekt herunter.
Grundlegendes zu MVVM
Die .NET MAUI-Entwicklerumgebung besteht in der Regel darin, eine Benutzeroberfläche in XAML zu erstellen und dann Code-Behind hinzuzufügen, der auf der Benutzeroberfläche arbeitet. Komplexe Wartungsprobleme können auftreten, wenn Apps geändert werden und größer und umfangreicher werden. Diese Probleme schließen die enge Kopplung zwischen den UI-Steuerelementen und der Geschäftslogik ein, die die Kosten für UI-Änderungen erhöht, sowie die Schwierigkeit der Durchführung von Komponententests für solchen Code.
Das Model-View-Viewmodel (MVVM)-Muster hilft, die Geschäfts- und Präsentationslogik einer Anwendung sauber von der Benutzeroberfläche (UI) zu trennen. Eine saubere Trennung zwischen App-Logik und Benutzeroberfläche hilft bei der Lösung zahlreicher Entwicklungsprobleme und erleichtert das Testen, Warten und Weiterentwickeln einer Anwendung. Es kann auch die Möglichkeiten der Wiederverwendung von Code erheblich verbessern und ermöglicht Entwicklern und UI-Designern eine einfachere Zusammenarbeit bei der Entwicklung ihrer jeweiligen Teile einer App.
Das Muster
Das MVVM-Muster umfasst drei Kernkomponenten: das Modell (Model), die Ansicht (View) und das Ansichtsmodell (ViewModel). Diese dienen jeweils einem bestimmten Zweck. Das folgende Diagramm zeigt die Beziehungen zwischen den drei Komponenten.
Es ist nicht nur wichtig, zu verstehen, wofür die einzelnen Komponenten zuständig sind, sondern auch, wie sie miteinander interagieren. Ganz allgemein ist die Ansicht über das Ansichtsmodell informiert, und das Ansichtsmodell ist über das Modell informiert, das Modell ist allerdings nicht über das Ansichtsmodell und das Ansichtsmodell nicht über die Ansicht informiert. Das Ansichtsmodell isoliert also die Ansicht vom Modell und ermöglicht die Entwicklung des Modells unabhängig von der Ansicht.
Der Schlüssel zur effektiven Verwendung von MVVM liegt darin, zu verstehen, wie App-Code in die richtigen Klassen integriert wird und wie die Klassen miteinander interagieren.
Ansicht
Die Ansicht dient zum Definieren der Struktur, des Layouts und der Darstellung dessen, was dem/der Benutzer*in auf dem Bildschirm angezeigt wird. Im Idealfall ist jede Ansicht in XAML definiert, mit einem begrenzten Code-Behind, der keine Geschäftslogik enthält. Manchmal enthält das CodeBehind jedoch Benutzeroberflächenlogik, um visuelles Verhalten zu implementieren, das in XAML nur schwer auszudrücken ist (beispielsweise Animationen).
ViewModel
Das Ansichtsmodell implementiert Eigenschaften und Befehle, an die die Ansicht Daten binden kann, und informiert die Ansicht mithilfe von Änderungsbenachrichtigungsereignissen über alle Zustandsänderungen. Die durch das Ansichtsmodell bereitgestellten Eigenschaften und Befehle definieren zwar die auf der Benutzeroberfläche angebotenen Funktionen, aber die Ansicht steuert, wie diese Funktionen angezeigt werden.
Das Ansichtsmodell ist auch für die Koordination der Interaktionen der Ansicht mit allen erforderlichen Modellklassen zuständig. Zwischen dem Ansichtsmodell und den Modellklassen besteht in der Regel eine 1:n-Beziehung.
Jedes Ansichtsmodell stellt Daten aus einem Modell in einem Format bereit, das von der Ansicht problemlos genutzt werden kann. Hierzu führt das Ansichtsmodell manchmal Datenkonvertierungen durch. Es empfiehlt sich, diese Datenkonvertierung im Ansichtsmodell zu platzieren, da sie Eigenschaften bereitstellt, die von der Ansicht zu Bindungszwecken genutzt werden können. So kann das Ansichtsmodell beispielsweise die Werte von zwei Eigenschaften kombinieren, um die Anzeige durch die Ansicht zu erleichtern.
Wichtig
NET MAUI marshallt Bindungsaktualisierungen an den UI-Thread. Bei der Verwendung von MVVM können Sie datengebundene ViewModel-Eigenschaften von jedem Thread aus aktualisieren, wobei die Binde-Engine von .NET MAUI die Aktualisierungen an den UI-Thread weiterleitet.
Modell
Modellklassen sind nicht visuelle Klassen, die die Daten der App kapseln. Das Modell kann somit als Darstellung des Domänenmodells der App betrachtet werden, das in der Regel ein Datenmodell zusammen mit Geschäfts- und Validierungslogik enthält.
Aktualisieren des Modells
In diesem ersten Teil des Tutorials werden Sie das Model-View-Viewmodel (MVVM) Muster implementieren. Öffnen Sie zunächst die Projektmappe Notes.sln in Visual Studio.
Bereinigen des Modells
Im vorherigen Tutorial fungierten die Modelltypen sowohl als Modell (Daten) als auch als ViewModel (Datenaufbereitung), das direkt auf eine Ansicht abgebildet wurde. Die folgende Tabelle beschreibt das Modell:
Codedatei | Beschreibung |
---|---|
Models/About.cs | Das About -Modell Enthält schreibgeschützte Felder, die die App selbst beschreiben, z. B. den App-Titel und die Version. |
Models/Note.cs | Das Note -Modell Stellt eine Notiz dar. |
Models/AllNotes.cs | Das AllNotes -Modell Lädt alle Notizen auf dem Gerät in eine Sammlung. |
Bei der App selbst gibt es nur eine einzige Dateneinheit, die von der App verwendet wird, nämlich Note
. Notizen werden vom Gerät geladen, auf dem Gerät gespeichert und über die App-UI bearbeitet. Für die Modelle About
und AllNotes
gibt es keinen wirklichen Bedarf. Entfernen Sie diese Modelle aus dem Projekt:
- Suchen Sie das Fenster Projektmappen-Explorer von Visual Studio.
- Klicken Sie mit der rechten Maustaste auf die Datei Modelle\About.cs und wählen Sie Löschen. Drücken Sie OK, um die Datei zu löschen.
- Klicken Sie mit der rechten Maustaste auf die Datei Modelle\AllNotes.cs und wählen Sie Löschen. Drücken Sie OK, um die Datei zu löschen.
Die einzige verbleibende Modelldatei ist die Datei Models\Note.cs.
Aktualisieren des Modells
Das Modell Note
enthält:
- Einen eindeutigen Bezeichner, bei dem es sich um den Dateinamen der Notiz handelt, die auf dem Gerät gespeichert ist.
- Den Text der Notiz.
- Ein Datum, das angibt, wann die Notiz erstellt oder zuletzt aktualisiert wurde.
Derzeit erfolgt das Laden und Speichern des Modells über die Ansichten und in einigen Fällen auch über die anderen Modelltypen, die Sie gerade entfernt haben. Der Code, den Sie für den Typ Note
haben, sollte der folgende sein:
namespace Notes.Models;
internal class Note
{
public string Filename { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
Das Note
-Modell wird erweitert, um das Laden, Speichern und Löschen von Notizen zu behandeln.
Doppelklicken Sie im Fensterbereich Projektmappen-Explorer von Visual Studio auf Models\Note.cs.
Fügen Sie im Code-Editor die folgenden beiden Methoden zur Klasse
Note
hinzu. Diese Methoden sind instanzbasiert und behandeln das Speichern oder Löschen der aktuellen Notiz auf bzw. vom Gerät: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));
Die App muss Notizen auf zwei Arten laden, eine einzelne Notiz aus einer Datei laden und alle Notizen auf dem Gerät laden. Der Code für das Laden kann aus
static
-Members bestehen, die keine Klasseninstanz zur Ausführung benötigen.Fügen Sie der Klasse den folgenden Code hinzu, um eine Notiz anhand des Dateinamens zu laden:
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) }; }
Dieser Code nimmt den Dateinamen als Parameter, erstellt den Pfad zu dem Ort, an dem die Notizen auf dem Gerät gespeichert sind, und versucht, die Datei zu laden, wenn sie existiert.
Die zweite Möglichkeit zum Laden von Notizen besteht darin, alle Notizen auf dem Gerät aufzulisten und in eine Sammlung zu laden.
Fügen Sie der Klasse folgenden Code hinzu:
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); }
Dieser Code gibt eine aufzählbare Sammlung von
Note
-Modelltypen zurück, indem er die Dateien auf dem Gerät abruft, die dem Dateimuster notes entsprechen: *.notes.txt. Jeder Dateiname wird an dieLoad
-Methode übergeben, wobei eine einzelne Notiz geladen wird. Schließlich wird die Sammlung von Notizen nach dem Datum jeder Notiz sortiert und an den Aufrufer zurückgegeben.Fügen Sie schließlich der Klasse einen Konstruktor hinzu, der die Standardwerte für die Eigenschaften festlegt, einschließlich eines zufälligen Dateinamens:
public Note() { Filename = $"{Path.GetRandomFileName()}.notes.txt"; Date = DateTime.Now; Text = ""; }
Der Code der Klasse Note
sollte wie folgt aussehen:
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);
}
}
Nachdem das Note
-Modell abgeschlossen ist, können die Ansichtsmodelle erstellt werden.
Erstellen Sie das About viewmodel
Bevor Sie View-Modelle zum Projekt hinzufügen, fügen Sie einen Verweis auf das MVVM Community Toolkit hinzu. Diese Bibliothek ist in NuGet verfügbar und stellt Typen und Systeme bereit, mit denen das MVVM-Muster implementiert werden kann.
Klicken Sie im Bereich Projektmappen-Explorer von Visual Studio mit der rechten Maustaste auf das Projekt Notizen >NuGet Pakete verwalten.
Wählen Sie die Registerkarte Durchsuchen aus.
Suchen Sie nach communitytoolkit mvvm und wählen Sie das Paket
CommunityToolkit.Mvvm
aus, das das erste Ergebnis sein sollte.Stellen Sie sicher, dass mindestens Version 8 ausgewählt ist. Dieses Tutorial wurde mit der Version 8.0.0 geschrieben.
Wählen Sie dann Installieren aus und akzeptieren Sie alle angezeigten Aufforderungen.
Jetzt können Sie mit dem Aktualisieren des Projekts beginnen, indem Sie Ansichtsmodelle hinzufügen.
Entkoppeln mit Ansichtsmodellen
Die View-to-Viewmodel-Beziehung basiert stark auf dem Bindungssystem, das von .NET Multi-platform App UI (.NET MAUI) bereitgestellt wird. Die App verwendet bereits eine Bindung in den Ansichten, um eine Liste von Notizen anzuzeigen und den Text und das Datum einer einzelnen Notiz darzustellen. Die App-Logik wird derzeit vom CodeBehind der Ansicht bereitgestellt und ist direkt an die Ansicht gebunden. Wenn ein Benutzer zum Beispiel eine Notiz bearbeitet und die Schaltfläche Speichern drückt, wird das Ereignis Clicked
für die Schaltfläche ausgelöst. Anschließend speichert der Code für den Ereignishandler den Notiztext in einer Datei und navigiert zum vorherigen Bildschirm.
Die App-Logik im CodeBehind einer Ansicht kann zu einem Problem werden, wenn sich die Ansicht ändert. Wenn z. B. die Schaltfläche durch ein anderes Eingabefeld ersetzt oder der Name eines Steuerelements geändert wird, können die Ereignishandler ungültig werden. Unabhängig davon, wie die Ansicht entworfen wurde, besteht der Zweck der Ansicht darin, eine Art App-Logik aufzurufen und dem Benutzer Informationen zu präsentieren. Bei dieser App speichert die Schaltfläche die Save
-Notiz und navigiert dann zurück zum vorherigen Bildschirm.
Das Viewmodel bietet der Anwendung einen bestimmten Platz für die App-Logik, unabhängig davon, wie die Benutzeroberfläche gestaltet ist oder wie die Daten geladen oder gespeichert werden. Das Viewmodel ist der Klebstoff, der das Datenmodell im Namen des Views darstellt und mit ihm interagiert.
Die Ansichtsmodelle werden in einem Ordner ViewModels gespeichert.
- Suchen Sie das Fenster Projektmappen-Explorer von Visual Studio.
- Klicken Sie mit der rechten Maustaste auf das Projekt Notizen und wählen Sie Hinzufügen>Neuer Ordner aus. Benennen Sie den Ordner ViewModels.
- Klicken Sie mit der rechten Maustaste auf den Ordner Ansichtsmodelle >Hinzufügen>Klasse und nennen Sie sie AboutViewModel.cs.
- Wiederholen Sie den vorherigen Schritt und erstellen Sie zwei weitere Ansichtsmodelle:
- NoteViewModel.cs
- NotesViewModel.cs
Die Projektstruktur sollte nun so wie auf dem folgenden Bild aussehen:
Informationen zum Ansichtsmodell und zur Infoansicht
Die Übersicht zeigt einige Daten auf dem Bildschirm an und navigiert optional zu einer Website mit weiteren Informationen. Da diese Ansicht keine Daten zu ändern hat, z. B. mit einem Texteingabesteuerelement oder dem Auswählen von Elementen aus einer Liste, ist es ein guter Kandidat, um das Hinzufügen eines Ansichtsmodells zu veranschaulichen. Für das Übersichtsmodel gibt es kein Sicherungsmodell.
Erstellen Sie das Übersichtsmodell:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf ViewModels\AboutViewModel.cs.
Fügen Sie den folgenden Code ein:
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); }
Der vorherige Codeausschnitt enthält einige Eigenschaften, die Informationen zur App darstellen, z. B. den Namen und die Version. Dieser Ausschnitt entspricht genau dem About-Modell, das Sie zuvor gelöscht haben. Dieses Viewmodel enthält jedoch ein neues Konzept, nämlich die ShowMoreInfoCommand
Befehlseigenschaft.
Befehle sind bindbare Aktionen, die Code aufrufen, und eignen sich hervorragend, um App-Logik unterzubringen. In diesem Beispiel verweist ShowMoreInfoCommand
auf die Methode ShowMoreInfo
, die den Webbrowser auf eine bestimmte Seite öffnet. Mehr über das Befehlssystem erfahren Sie im nächsten Abschnitt.
Informationen zur Ansicht
Die Infoansicht muss leicht geändert werden, um sie mit dem im vorherigen Abschnitt erstellten Ansichtsmodell zu verknüpfen. Wenden Sie in der Datei Views\AboutPage.xaml die folgenden Änderungen an:
- Aktualisieren Sie den
xmlns:models
XML Namespace aufxmlns:viewModels
und richten Sie denNotes.ViewModels
.NET Namespace ein. - Ändern Sie die
ContentPage.BindingContext
-Eigenschaft in eine neue Instanz desAbout
-Viewmodels. - Entfernen Sie den
Clicked
-Ereignishandler der Schaltfläche und verwenden Sie dieCommand
-Eigenschaft.
Aktualisieren Sie die Ansicht About View:
Doppelklicken Sie im Fensterbereich Solution Explorer von Visual Studio auf Views\AboutPage.xaml.
Fügen Sie den folgenden Code ein:
<?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>
Der vorherige Codeausschnitt hebt die Zeilen hervor, die in dieser Version der Ansicht geändert wurden.
Beachten Sie, dass für die Schaltfläche die Eigenschaft Command
verwendet wird. Viele Steuerelemente haben eine Command
-Eigenschaft, die aufgerufen wird, wenn der Benutzende mit dem Steuerelement interagiert. Bei der Verwendung mit einer Schaltfläche wird der Befehl aufgerufen, wenn ein Benutzer die Schaltfläche drückt, ähnlich wie der Clicked
-Ereignishandler aufgerufen wird, mit dem Unterschied, dass Sie Command
an eine Eigenschaft im Viewmodel binden können.
In dieser Ansicht wird die Command
aufgerufen, wenn der Benutzende auf die Schaltfläche drückt. Die Command
ist an die ShowMoreInfoCommand
-Eigenschaft im Viewmodel gebunden und führt bei ihrem Aufruf den Code in der ShowMoreInfo
-Methode aus, die den Webbrowser zu einer bestimmten Seite öffnet.
Bereinigen der Informationen zu CodeBehind
Die ShowMoreInfo
-Schaltfläche verwendet den Event-Handler nicht, daher sollte der LearnMore_Clicked
-Code aus der Datei Views\AboutPage.xaml.cs entfernt werden. Löschen Sie diesen Code, die Klasse sollte nur den Konstruktor enthalten:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf Views\AboutPage.xaml.cs.
Tipp
Möglicherweise müssen Sie „Views\AboutPage.xaml“ erweitern, um die Datei anzuzeigen.
Ersetzen Sie den Code durch den folgenden Ausschnitt:
namespace Notes.Views; public partial class AboutPage : ContentPage { public AboutPage() { InitializeComponent(); } }
Erstellen des Notizenansichtsmodells
Das Ziel der Aktualisierung der Notizansicht ist es, so viel Funktionalität wie möglich aus dem XAML-Code-Behind zu entfernen und in das Notiz-Viewmodel zu verlagern.
Notizansichtsmodell
Basierend auf den Anforderungen der Notizansicht muss das Notizansichtsmodell die folgenden Elemente bereitstellen:
- Den Text der Notiz
- Das Datum/die Uhrzeit, zu dem die Notiz erstellt oder zuletzt aktualisiert wurde
- Einen Befehl, der die Notiz speichert
- Einen Befehl, der die Notiz löscht
Erstellen Sie das Notizansichtsmodell:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf ViewModels\NoteViewModel.cs.
Ersetzen Sie den Code in dieser Datei durch den folgenden Ausschnitt:
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.ComponentModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NoteViewModel : ObservableObject, IQueryAttributable { private Models.Note _note; }
Dieser Code ist das leere
Note
-Ansichtsmodell, in das Sie Eigenschaften und Befehle zur Unterstützung derNote
-Ansicht einfügen werden. Beachten Sie, dass der NamespaceCommunityToolkit.Mvvm.ComponentModel
importiert wird. Dieser Namespace bietet dieObservableObject
, die als Basisklasse verwendet wird. Im nächsten Schritt werden Sie mehr überObservableObject
erfahren. DerCommunityToolkit.Mvvm.Input
-Namespace wird ebenfalls importiert. Dieser Namespace stellt einige Befehlstypen bereit, die Methoden asynchron aufrufen.Das
Models.Note
-Modell wird als privates Feld gespeichert. Die Eigenschaften und Methoden dieser Klasse verwenden dieses Feld.Fügen Sie der Klasse die folgenden Eigenschaften hinzu:
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;
Die Eigenschaften
Date
undIdentifier
sind einfache Eigenschaften, die lediglich die entsprechenden Werte aus dem Modell abrufen.Tipp
Bei Eigenschaften erzeugt die
=>
-Syntax eine „get-only“-Eigenschaft, bei der die Anweisung rechts von=>
einen Wert ergeben muss, der zurückgegeben werden soll.Die
Text
-Eigenschaft überprüft zuerst, ob der festgelegte Wert ein anderer Wert ist. Wenn der Wert anders ist, wird dieser Wert an die Eigenschaft des Modells übergeben, und dieOnPropertyChanged
-Methode wird aufgerufen.Die
OnPropertyChanged
-Methode wird von derObservableObject
-Basisklasse bereitgestellt. Diese Methode verwendet den Namen des aufrufenden Codes, in diesem Fall den Eigenschaftsnamen von Text, und löst das EreignisObservableObject.PropertyChanged
aus. Dieses Ereignis stellt den Namen der Eigenschaft für alle Ereignisabonnenten bereit. Das von .NET MAUI bereitgestellte Bindungssystem erkennt dieses Ereignis und aktualisiert alle zugehörigen Bindungen auf der Benutzeroberfläche. Für das Notizansichtsmodell wird das Ereignis ausgelöst, wenn sich dieText
-Eigenschaft ändert, und jedes UI-Element, das an dieText
-Eigenschaft gebunden ist, wird über die Änderung der Eigenschaft informiert.Fügen Sie der Klasse die folgenden Befehlseigenschaften hinzu, bei denen es sich um die Befehle handelt, an die die Ansicht gebunden werden kann:
public ICommand SaveCommand { get; private set; } public ICommand DeleteCommand { get; private set; }
Fügen Sie der Klasse die folgenden Konstruktoren hinzu:
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); }
Diese beiden Konstruktoren werden verwendet, um entweder das Viewmodel mit einem neuen Sicherungsmodell zu erstellen, das eine leere Notiz ist, oder um ein Viewmodel zu erstellen, das die angegebene Modellinstanz verwendet.
Die Konstruktoren richten auch die Befehle für das Ansichtsmodell ein. Fügen Sie als Nächstes den Code für diese Befehle hinzu.
Fügen Sie die Methoden
Save
undDelete
hinzu: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}"); }
Diese Methoden werden von zugehörigen Befehlen aufgerufen. Sie führen die entsprechenden Aktionen am Modell aus und veranlassen die App, zur vorherigen Seite zu navigieren. Dem
..
-Navigationspfad wird ein Abfragezeichnefolge-Parameter hinzugefügt, der angibt, welche Aktion durchgeführt wurde, und den eindeutigen Bezeichner der Notiz enthält.Fügen Sie als Nächstes der Klasse die
ApplyQueryAttributes
-Methode hinzu, die den Anforderungen der IQueryAttributable-Schnittstelle entspricht:void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { if (query.ContainsKey("load")) { _note = Models.Note.Load(query["load"].ToString()); RefreshProperties(); } }
Wenn eine Seite oder der Bindungskontext einer Seite diese Schnittstelle implementiert, werden die in der Navigation verwendeten Abfragezeichenfolge-Parameter an die Methode
ApplyQueryAttributes
übergeben. Dieses Viewmodel wird als Bindungskontext für die Notizansicht verwendet. Wenn zur Notizansicht navigiert wird, wird der Bindungskontext der Ansicht (dieses Viewmodel) mit den während der Navigation verwendeten Abfragezeichenfolge-Parametern versorgt.Dieser Code überprüft, ob der
load
-Schlüssel imquery
-Wörterbuch bereitgestellt wurde. Wenn dieser Schlüssel gefunden wird, sollte der Wert des Bezeichners (der Dateiname) der zu ladenden Notiz sein. Dieser Hinweis wird geladen und als zugrunde liegendes Modellobjekt dieser Viewmodel-Instanz festgelegt.Fügen Sie schließlich die folgenden beiden Hilfsmethoden zur Klasse hinzu:
public void Reload() { _note = Models.Note.Load(_note.Filename); RefreshProperties(); } private void RefreshProperties() { OnPropertyChanged(nameof(Text)); OnPropertyChanged(nameof(Date)); }
Die
Reload
-Methode ist eine Hilfsmethode, die das Sicherungsmodellobjekt aktualisiert und aus dem Gerätespeicher neu geladen wird.Die
RefreshProperties
-Methode ist eine weitere Hilfsmethode, die sicherstellt, dass alle an dieses Objekt gebundenen Abonnenten darüber informiert werden, dass sich dieText
- undDate
-Eigenschaften geändert haben. Da das zugrunde liegende Modell (das_note
-Feld) geändert wird, wenn die Notiz während der Navigation geladen wird, werden die EigenschaftenText
undDate
nicht tatsächlich auf neue Werte gesetzt. Da diese Eigenschaften nicht direkt gesetzt werden, würden alle Bindungen, die mit diesen Eigenschaften verbunden sind, nicht benachrichtigt werden, daOnPropertyChanged
nicht für jede Eigenschaft aufgerufen wird.RefreshProperties
stellt sicher, dass Bindungen an diese Eigenschaften aktualisiert werden.
Der Code für die Klasse sollte wie der folgende Ausschnitt aussehen:
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));
}
}
Notizansicht
Nachdem das Viewmodel erstellt wurde, aktualisieren Sie die Note view. Wenden Sie in der Datei Views\NotePage.xaml die folgenden Änderungen an:
- Fügen Sie den
xmlns:viewModels
XML Namespace hinzu, der auf denNotes.ViewModels
.NET Namespace ausgerichtet ist. - Fügen Sie der Seite ein
BindingContext
hinzu. - Entfernen Sie die Ereignishandler für die Schaltflächen
Clicked
Löschen und Speichern und ersetzen Sie sie durch Befehle.
Aktualisieren Sie die Notizansicht:
- Doppelklicken Sie im Fensterbereich Projektmappen-Explorer von Visual Studio auf Views\NotePage.xaml, um den XAML-Editor zu öffnen.
- Fügen Sie den folgenden Code ein:
<?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>
Zuvor hat diese Ansicht keinen Bindungskontext deklariert, da sie vom CodeBehind der Seite selbst bereitgestellt wurde. Das direkte Festlegen des Bindungskontexts im XAML-Code bietet zwei Dinge:
Wenn die Seite zur Laufzeit navigiert wird, wird eine leere Notiz angezeigt. Dies liegt daran, dass der parameterlose Konstruktor für den Bindungskontext, das Viewmodel, aufgerufen wird. Wie Sie sich sicher erinnern, erzeugt der parameterlose Konstruktor für das Notizansichtsmodell eine leere Notiz.
Die Intellisense-Funktion im XAML-Editor zeigt die verfügbaren Eigenschaften an, sobald Sie mit der Eingabe der
{Binding
-Syntax beginnen. Die Syntax wird ebenfalls überprüft und benachrichtigt Sie über einen ungültigen Wert. Versuchen Sie, die Bindungssyntax fürSaveCommand
inSave123Command
zu ändern. Wenn Sie den Mauszeiger über den Text bewegen, werden Sie feststellen, dass ein Tooltip angezeigt wird, der Sie darüber informiert, dass Save123Command nicht gefunden wurde. Diese Benachrichtigung wird nicht als Fehler betrachtet, da Bindungen dynamisch sind, es ist wirklich eine kleine Warnung, die Ihnen beim Eingeben der falschen Eigenschaft helfen kann.Wenn Sie den SaveCommand auf einen anderen Wert geändert haben, stellen Sie ihn jetzt wieder her.
Bereinigen des CodeBehinds für Notizen
Da sich die Interaktion mit der Ansicht nun von Ereignishandlern zu Befehlen geändert hat, öffnen Sie die Datei Views\NotePage.xaml.cs und ersetzen Sie den gesamten Code durch eine Klasse, die nur den Konstruktor enthält:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf Views\NotePage.xaml.cs.
Tipp
Möglicherweise müssen Sie Views\NotePage.xaml erweitern, um die Datei anzuzeigen.
Ersetzen Sie den Code durch den folgenden Ausschnitt:
namespace Notes.Views; public partial class NotePage : ContentPage { public NotePage() { InitializeComponent(); } }
Erstellen des Notizenansichtsmodells
Das letzte Viewmodel-View-Paar ist das Notizansichtsmodell und die AllNotes view. Derzeit ist die Ansicht jedoch direkt an das Modell gebunden, das zu Beginn des Tutorials gelöscht wurde. Das Ziel bei der Aktualisierung der AllNotes-Ansicht ist es, so viel Funktionalität wie möglich aus dem XAML-Code-Behind herauszuholen und in das Viewmodel zu integrieren. Auch hier ist der Vorteil, dass die Ansicht ihren Entwurf mit geringem Effekt in Ihrem Code ändern kann.
Notizenansichtsmodell
Je nachdem, was die AllNotes-Ansicht anzeigen soll und welche Interaktionen der Benutzer durchführen wird, muss das Notizenansichtsmodell die folgenden Elemente bereitstellen:
- Eine Sammlung von Notizen
- Einen Befehl zum Behandeln der Navigation zu einer Notiz
- Einen Befehl zum Erstellen einer neuen Notiz
- Aktualisieren Sie die Liste der Notizen, wenn eine erstellt, gelöscht oder geändert wird.
Erstellen Sie das Notizenansichtsmodell:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf ViewModels\NotesViewModel.cs.
Ersetzen Sie den Code in dieser Datei durch den folgenden Code:
using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NotesViewModel: IQueryAttributable { }
Dieser Code ist der leere
NotesViewModel
, in den Sie Eigenschaften und Befehle zur Unterstützung derAllNotes
-Ansicht einfügen werden.Fügen Sie im Code der Klasse
NotesViewModel
die folgenden Eigenschaften hinzu:public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; } public ICommand NewCommand { get; } public ICommand SelectNoteCommand { get; }
Die
AllNotes
-Eigenschaft ist eineObservableCollection
-Eigenschaft, die alle vom Gerät geladenen Notizen speichert. Die beiden Befehle werden von der Ansicht verwendet, um die Aktionen zum Erstellen einer Notiz oder Auswählen einer vorhandenen Notiz auszulösen.Fügen Sie der Klasse einen parameterlosen Konstruktor hinzu, der die Befehle initialisiert und die Notizen aus dem Modell lädt:
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); }
Beachten Sie, dass die Sammlung
AllNotes
die MethodeModels.Note.LoadAll
verwendet, um die beobachtbare Sammlung mit Notizen zu füllen. DieLoadAll
-Methode gibt die Notizen alsModels.Note
-Typ zurück, aber die beobachtbare Sammlung ist eine Sammlung vonViewModels.NoteViewModel
-Typen. Der Code verwendet die Linq-ErweiterungSelect
, um aus den vonLoadAll
zurückgegebenen Notizmodellen Viewmodel-Instanzen zu erstellen.Erstellen Sie die Methoden, auf die die Befehle abzielen:
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}"); }
Beachten Sie, dass die Methode
NewNoteAsync
keinen Parameter benötigt, die MethodeSelectNoteAsync
hingegen schon. Befehle können optional über einen einzelnen Parameter verfügen, der angegeben wird, wenn der Befehl aufgerufen wird. Für dieSelectNoteAsync
-Methode stellt der Parameter die Notiz dar, die ausgewählt wird.Implementieren Sie schließlich die
IQueryAttributable.ApplyQueryAttributes
-Methode: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))); } }
Das Notizenansichtsmodell, das im vorherigen Schritt des Tutorials erstellt wurde, verwendet die Navigation, wenn die Notiz gespeichert oder gelöscht wurde. Das Ansichtsmodell navigiert zurück zur AllNotes-Ansicht, der dieses Ansichtsmodell zugeordnet ist. Dieser Code erkennt, ob die Abfragezeichenfolge entweder den Schlüssel
deleted
odersaved
enthält. Der Wert des Schlüssels ist der eindeutige Bezeichner der Notiz.Wenn die Notiz gelöscht wurde, wird diese Notiz in der
AllNotes
-Sammlung mit dem angegebenen Bezeichner gefunden und entfernt.Es gibt zwei mögliche Gründe, warum eine Notiz gespeichert wird. Die Notiz wurde entweder soeben erstellt oder eine vorhandene Notiz geändert. Wenn sich die Notiz bereits in der
AllNotes
-Sammlung befindet, ist dies eine Notiz, die aktualisiert wurde. In diesem Fall muss die Instanz der Notiz in der Sammlung nur aktualisiert werden. Wenn die Notiz in der Sammlung fehlt, handelt es sich um eine neue Notiz, die der Sammlung hinzugefügt werden muss.
Der Code für die Klasse sollte wie der folgende Ausschnitt aussehen:
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)));
}
}
}
AllNotes-Ansicht
Nachdem das Viewmodel erstellt wurde, aktualisieren Sie die AllNotes-Ansicht, um auf die Eigenschaften des Viewmodels zu verweisen. Wenden Sie in der Datei Views\AllNotesPage.xaml die folgenden Änderungen an:
- Fügen Sie den
xmlns:viewModels
XML Namespace hinzu, der auf denNotes.ViewModels
.NET Namespace ausgerichtet ist. - Fügen Sie der Seite ein
BindingContext
hinzu. - Entfernen Sie das Ereignis
Clicked
der Symbolleistenschaltfläche und verwenden Sie die EigenschaftCommand
. - Ändern Sie die
CollectionView
, um ihreItemSource
anAllNotes
zu binden. - Ändern Sie den
CollectionView
-Befehl, um zu reagieren, wenn sich das ausgewählte Element ändert.
Aktualisieren der AllNotes-Ansicht:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf Views\AllNotesPage.xaml.
Fügen Sie den folgenden Code ein:
<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>
Die Symbolleiste verwendet das Clicked
-Ereignis nicht mehr und verwendet stattdessen einen Befehl.
Die CollectionView
unterstützt die Befehle mit den Eigenschaften SelectionChangedCommand
und SelectionChangedCommandParameter
. In der aktualisierten XAML ist die SelectionChangedCommand
-Eigenschaft an die SelectNoteCommand
-Eigenschaft des Viewmodels gebunden, was bedeutet, dass der Befehl aufgerufen wird, wenn sich das ausgewählte Element ändert. Wenn der Befehl aufgerufen wird, wird der Wert der Eigenschaft SelectionChangedCommandParameter
an den Befehl übergeben.
Sehen Sie sich die für CollectionView
verwendete Bindung an:
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding AllNotes}"
Margin="20"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectNoteCommand}"
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
Die Eigenschaft SelectionChangedCommandParameter
verwendet die Bindung Source={RelativeSource Self}
. Der Self
verweist auf das aktuelle Objekt, das der CollectionView
ist. Beachten Sie, dass der Bindungspfad die Eigenschaft SelectedItem
ist. Wenn der Befehl durch Ändern des ausgewählten Elements aufgerufen wird, wird der Befehl SelectNoteCommand
aufgerufen und das ausgewählte Element wird als Parameter an den Befehl übergeben.
Bereinigen von AllNotes-CodeBehind
Da sich die Interaktion mit der Ansicht nun von Ereignishandlern zu Befehlen geändert hat, öffnen Sie die Datei Views\AllNotesPage.xaml.cs und ersetzen Sie den gesamten Code durch eine Klasse, die nur den Konstruktor enthält:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf Views\AllNotesPage.xaml.cs.
Tipp
Sie müssen möglicherweise Views\AllNotesPage.xaml erweitern, um die Datei anzuzeigen.
Ersetzen Sie den Code durch den folgenden Ausschnitt:
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); } }
Ausführen der App
Sie können die App jetzt ausführen, und alles funktioniert. Es gibt jedoch zwei Probleme mit dem Verhalten der App:
- Wenn Sie eine Notiz auswählen, den Editor öffnen, Speichern drücken und dann versuchen, dieselbe Notiz auszuwählen, funktioniert es nicht.
- Wenn eine Notiz geändert oder hinzugefügt wird, wird die Liste der Notizen nicht neu angeordnet, um die neuesten Notizen oben anzuzeigen.
Diese beiden Probleme werden im nächsten Schritt des Tutorials behoben.
Beheben des App-Verhaltens
Nachdem der App-Code kompiliert und ausgeführt werden kann, haben Sie wahrscheinlich festgestellt, dass es zwei Fehler gibt, mit denen sich die App verhält. Mit der App können Sie eine bereits ausgewählte Notiz nicht erneut auswählen, und die Liste der Notizen wird nicht neu angeordnet, nachdem eine Notiz erstellt oder geändert wurde.
Abrufen von Notizen zum Anfang der Liste
Beheben Sie zunächst das Neuanordnungsproblem mit der Notizenliste. In der Datei ViewModels\NotesViewModel.cs enthält die AllNotes
-Sammlung alle Notizen, die dem Benutzenden angezeigt werden sollen. Leider hat die Verwendung von ObservableCollection
den Nachteil, dass sie manuell sortiert werden muss. Führen Sie die folgenden Schritte aus, um die neuen oder aktualisierten Elemente am Anfang der Liste abzurufen:
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf ViewModels\NotesViewModel.cs.
Sehen Sie sich in der
ApplyQueryAttributes
-Methode die Logik für den Schlüssel gespeicherte Abfragezeichenfolge an.Wenn die
matchedNote
nichtnull
ist, wird die Notiz aktualisiert. Verwenden Sie dieAllNotes.Move
Methode, um denmatchedNote
Index 0 zu verschieben, bei dem es sich um den Anfang der Liste handelt.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); }
Die
AllNotes.Move
-Methode verwendet zwei Parameter, um die Position eines Objekts in der Auflistung zu verschieben. Der erste Parameter ist der Index des Objekts, das verschoben werden soll, und der zweite Parameter ist der Index, wohin das Objekt verschoben werden soll. DieAllNotes.IndexOf
-Methode ruft den Index der Notiz ab.Wenn die
matchedNote
null
ist, ist die Notiz neu und wird der Liste hinzugefügt. Anstatt sie hinzuzufügen, wodurch die Notiz am Ende der Liste angehängt wird, fügen Sie die Notiz bei Index 0 ein, also am Anfang der Liste. Ändern Sie die MethodeAllNotes.Add
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)));
Die ApplyQueryAttributes
-Methode sollte wie der folgende Codeausschnitt aussehen:
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)));
}
}
Zweimaliges Auswählen einer Notiz zulassen
In der AllNotes-Ansicht listet das CollectionView
alle Notizen auf, erlaubt es aber nicht, die gleiche Notiz zweimal auszuwählen. Es gibt zwei Möglichkeiten, wie das Element ausgewählt bleibt: wenn der Benutzende eine vorhandene Notiz ändert und wenn der Benutzende zwangsweise rückwärts navigiert. Der Fall, dass der Benutzende eine Notiz speichert, ist mit der Codeänderung im vorherigen Abschnitt, die AllNotes.Move
verwendet, behoben, sodass Sie sich in diesem Fall keine Sorgen machen müssen.
Das Problem, das Sie jetzt lösen müssen, bezieht sich auf die Navigation. Unabhängig davon, wie die Allnotes-Ansicht angesteuert wird, wird das Ereignis NavigatedTo
für die Seite ausgelöst. Dieses Ereignis ist ein idealer Ort, um das ausgewählte Element in der CollectionView
zwangsweise abzuwählen.
Da hier jedoch das MVVM-Muster angewandt wird, kann das Viewmodel nicht direkt etwas in der View auslösen, z. B. das Löschen des ausgewählten Elements nach dem Speichern der Notiz. Wie können Sie dies also erreichen? Eine gute Implementierung des MVVM-Musters minimiert den CodeBehind in der Ansicht. Es gibt einige verschiedene Möglichkeiten, dieses Problem zu lösen, um das MVVM-Trennungsmuster zu unterstützen. Es ist jedoch auch ok, Code in den CodeBehind der Ansicht einzufügen, insbesondere, wenn er direkt mit der Ansicht verknüpft ist. MVVM hat viele großartige Designs und Konzepte, die Ihnen helfen, Ihre App zu unterteilen, die Wartbarkeit zu verbessern und das Hinzufügen neuer Funktionen zu erleichtern. In einigen Fällen können Sie jedoch feststellen, dass MVVM ein Overengineering fördert.
Überarbeiten Sie keine Lösung für dieses Problem und verwenden Sie einfach das Ereignis NavigatedTo
, um das ausgewählte Element aus dem CollectionView
zu löschen.
Doppelklicken Sie im Bereich Projektmappen-Explorer von Visual Studio auf Views\AllNotesPage.xaml.
Fügen Sie in der XAML für die
<ContentPage>
das EreignisNavigatedTo
hinzu:<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>
Sie können einen Standard-Ereignishandler hinzufügen, indem Sie mit der rechten Maustaste auf den Namen der Ereignismethode,
ContentPage_NavigatedTo
, klicken und Gehe zu Definition auswählen. Diese Aktion öffnet die Views\AllNotesPage.xaml.cs im Code-Editor.Ersetzen Sie den Code der Ereignisbehandlung durch den folgenden Ausschnitt:
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { notesCollection.SelectedItem = null; }
In der XAML wurde die
CollectionView
mitnotesCollection
bezeichnet. Dieser Code verwendet diesen Namen, um aufCollectionView
zuzugreifen, und setztSelectedItem
aufnull
. Das ausgewählte Element wird jedes Mal gelöscht, wenn die Seite angewählt wird.
Führen Sie nun Ihre App aus. Versuchen Sie, zu einer Notiz zu navigieren, drücken Sie die Zurück-Taste, und wählen Sie dieselbe Notiz ein zweites Mal aus. Das App-Verhalten wurde behoben!
Entdecken Sie den Code für dieses Tutorial.. Wenn Sie eine Kopie des fertigen Projekts herunterladen möchten, um Ihren Code damit zu vergleichen, laden Sie dieses Projekt herunter.
Herzlichen Glückwunsch!
Ihre App verwendet jetzt MVVM-Muster!
Nächste Schritte
Unter den folgenden Links finden Sie weitere Informationen zu einigen Konzepten, die Sie in diesem Tutorial gelernt haben:
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.