Not
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här självstudien är utformad för att fortsätta på självstudien Skapa en .NET MAUI-app, vilken skapade en anteckningsapp. I den här självstudien får du lära dig att:
- Implementera MVVM-mönstret (model-view-viewmodel).
- Använd ytterligare ett frågeformat för att skicka data under navigeringen.
Vi rekommenderar att du först följer självstudien Skapa en .NET MAUI-app eftersom koden som skapades i den självstudien är grunden för den här självstudien. Om du har förlorat koden eller vill börja om från början laddar du ned det här projektet.
Förstå MVVM
.NET MAUI-utvecklarupplevelsen innebär vanligtvis att skapa ett användargränssnitt i XAML och sedan lägga till kod bakom som fungerar i användargränssnittet. Komplexa underhållsproblem kan uppstå när appar ändras och växer i storlek och omfattning. Dessa problem omfattar den snäva kopplingen mellan användargränssnittskontrollerna och affärslogik, vilket ökar kostnaden för att göra ändringar i användargränssnittet och svårigheten med enhetstestning av sådan kod.
MVVM-mönstret (model-view-viewmodel) hjälper till att tydligt separera en applikations affärslogik och presentationslogik från dess användargränssnitt (UI). Genom att upprätthålla en ren separation mellan applogik och användargränssnittet kan du lösa många utvecklingsproblem och göra en app enklare att testa, underhålla och utveckla. Det kan också avsevärt förbättra möjligheterna till återanvändning av kod och göra det möjligt för utvecklare och användargränssnittsdesigners att samarbeta enklare när de utvecklar sina respektive delar av en app.
Mönstret
Det finns tre kärnkomponenter i MVVM-mönstret: modellen, vyn och vymodellen. Var och en har ett distinkt syfte. Följande diagram visar relationerna mellan de tre komponenterna.
Förutom att förstå ansvarsområden för varje komponent är det också viktigt att förstå hur de interagerar. På hög nivå känner vyn "till" vymodellen och vymodellen "känner till" modellen, men modellen är omedveten om vymodellen och vymodellen känner inte till vyn. Därför isolerar vymodellen vyn från modellen och gör att modellen kan utvecklas oberoende av vyn.
Nyckeln till att använda MVVM ligger i att förstå hur appkod ska räknas in i rätt klasser och hur klasserna interagerar.
Utsikt
Vyn ansvarar för att definiera strukturen, layouten och utseendet på det som användaren ser på skärmen. Helst definieras varje vy i XAML, med en begränsad bakomliggande kod som inte innehåller affärslogik. Men i vissa fall kan koden bakom innehålla UI-logik som implementerar visuellt beteende som är svårt att uttrycka i XAML, till exempel animeringar.
ViewModel
Vymodellen implementerar egenskaper och kommandon som vyn kan binda data till och meddelar vyn om eventuella tillståndsändringar genom ändringsmeddelandehändelser. De egenskaper och kommandon som visningsmodellen tillhandahåller definierar de funktioner som ska erbjudas av användargränssnittet, men vyn avgör hur den funktionen ska visas.
Vymodellen ansvarar också för att samordna vyns interaktioner med alla modellklasser som krävs. Det finns vanligtvis en en-till-många-relation mellan vymodellen och modellklasserna.
Varje vymodell innehåller data från en modell i ett formulär som vyn enkelt kan använda. För att åstadkomma detta utför vymodellen ibland datakonvertering. Det är en bra idé att placera datakonverteringen i vymodellen eftersom den innehåller egenskaper som vyn kan binda till. Vymodellen kan till exempel kombinera värdena för två egenskaper så att den blir enklare att visa i vyn.
Viktigt!
.NET MAUI hanterar bindningsuppdateringar till UI-tråden. När du använder MVVM kan du uppdatera databundna viewmodel-egenskaper från valfri tråd, med .NET MAUI:s bindningsmotor som ger uppdateringarna till användargränssnittstråden.
Modell
Modellklasser är icke-visuella klasser som kapslar in appens data. Därför kan modellen anses representera appens domänmodell, som vanligtvis innehåller en datamodell tillsammans med affärs- och valideringslogik.
Uppdatera modellen
I den första delen av handledningen implementerar du MVVM-mönstret (model-view-viewmodel). Starta genom att öppna lösningen Notes.sln i Visual Studio.
Rensa modellen
I den föregående tutorialen fungerade modelltyperna både som modell (data) och som en vy-modell (dataförberedelse), och mappades direkt till en vy. I följande tabell beskrivs modellen:
| Kodfil | Beskrivning |
|---|---|
| Models/About.cs | Modellen About . Innehåller skrivskyddade fält som beskriver själva appen, till exempel appens titel och version. |
| Models/Note.cs | Modellen Note . Representerar en anteckning. |
| Modeller/AllNotes.cs | Modellen AllNotes . Läser in alla anteckningar på enheten till en samling. |
När du tänker på själva appen, använder appen endast en datadel, Note. Anteckningar läses in från enheten, sparas på enheten och redigeras via appens användargränssnitt. Det finns verkligen inget behov av About modellerna och AllNotes . Ta bort dessa modeller från projektet:
- Leta upp solution explorer-fönstret i Visual Studio.
- Högerklicka på filen Models\About.cs och välj Ta bort. Tryck på OK för att ta bort filen.
- Högerklicka på filen Models\AllNotes.cs och välj Ta bort. Tryck på OK för att ta bort filen.
Den enda modellfil som återstår är filen Models\Note.cs .
Uppdatera modellen
Modellen Note innehåller:
- En unik identifierare, som är filnamnet på anteckningen som lagras på enheten.
- Texten i anteckningen.
- Ett datum som anger när anteckningen skapades eller senast uppdaterades.
För närvarande utförs inläsning och sparande av modellen via vyerna, och i vissa fall av andra modelltyper som du just tagit bort. Koden som du har för typen Note bör vara följande:
namespace Notes.Models;
internal class Note
{
public string Filename { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
Modellen Note kommer att utökas för att hantera inläsning, sparande och borttagning av anteckningar.
Dubbelklicka på Modeller\Note.cs i fönstret Solution Explorer i Visual Studio.
Lägg till följande två metoder i
Noteklassen i kodredigeraren. Dessa metoder är instansbaserade och hanterar sparande eller borttagning av den aktuella anteckningen till eller från enheten: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));Appen måste läsa in anteckningar på två sätt, läsa in en enskild anteckning från en fil och läsa in alla anteckningar på enheten. Koden för att hantera inläsning kan vara
staticmedlemmar och kräver inte att en klassinstans körs.Lägg till följande kod i klassen för att läsa in en anteckning efter filnamn:
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) }; }Den här koden tar filnamnet som en parameter, skapar sökvägen till den plats där anteckningarna lagras på enheten och försöker läsa in filen om den finns.
Det andra sättet att läsa in anteckningar är att räkna upp alla anteckningar på enheten och läsa in dem i en samling.
Lägg till följande kod i klassen:
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); }Den här koden returnerar en uppräkningsbar samling
Notemodelltyper genom att hämta filerna på enheten som matchar anteckningsfilmönstret: *.notes.txt. Varje filnamn skickas tillLoadmetoden och läser in en enskild anteckning. Slutligen sorteras insamlingen av anteckningar efter datumet för varje anteckning och returneras till anroparen.Slutligen lägger du till en konstruktor i klassen som anger standardvärdena för egenskaperna, inklusive ett slumpmässigt filnamn:
public Note() { Filename = $"{Path.GetRandomFileName()}.notes.txt"; Date = DateTime.Now; Text = ""; }
Klasskoden Note bör se ut så här:
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);
}
}
Nu när Note modellen är klar kan vymodellerna skapas.
Skapa vyrutan Om
Innan du lägger till visningsmodeller i projektet lägger du till en referens till MVVM Community Toolkit. Det här biblioteket är tillgängligt på NuGet och innehåller typer och system som hjälper dig att implementera MVVM-mönstret.
I fönstret Solution Explorer i Visual Studio högerklickar du på Notes-projektet>Hantera NuGet-paket.
Välj fliken Bläddra.
Sök efter communitytoolkit mvvm och välj
CommunityToolkit.Mvvmpaketet, som ska vara det första resultatet.Kontrollera att minst version 8 är markerad. Den här handledningen skrevs med version 8.0.0.
Välj sedan Installera och acceptera alla frågor som visas.
Nu är du redo att börja uppdatera projektet genom att lägga till vymodeller.
Frikoppla med visningsmodeller
View-to-viewmodel-relationen är starkt beroende av bindningssystemet som tillhandahålls av .NET Multi-platform App UI (.NET MAUI). Appen använder redan bindning i vyerna för att visa en lista med anteckningar och för att presentera texten och datumet för en enskild anteckning. Applogik tillhandahålls för närvarande av vyns kod bakom och är direkt kopplad till vyn. När en användare till exempel redigerar en anteckning och trycker på knappen Spara aktiveras Clicked händelsen för knappen. Sedan sparar koden bakom för händelsehanteraren anteckningstexten i en fil och navigerar tillbaka till föregående skärm.
Att ha applogik i koden bakom en vy kan bli ett problem när vyn ändras. Om knappen till exempel ersätts med en annan indatakontroll, eller om namnet på en kontroll ändras, kan händelsehanterare bli ogiltiga. Oavsett hur vyn är utformad är syftet med vyn att anropa någon form av applogik och att presentera information för användaren. För den här appen Save sparar knappen anteckningen och navigerar sedan tillbaka till föregående skärm.
Viewmodel ger appen en specifik plats för att placera applogik oavsett hur användargränssnittet är utformat eller hur data läses in eller sparas. Viewmodel är limmet som representerar och interagerar med datamodellen för vyns räkning.
Vymodellerna lagras i en ViewModels-mapp .
- Leta upp solution explorer-fönstret i Visual Studio.
- Högerklicka på projektet Anteckningar och välj Lägg till>ny mapp. Ge mappen namnet ViewModels.
- Högerklicka på mappen >Lägg till>klass och ge den namnet AboutViewModel.cs.
- Upprepa föregående steg och skapa ytterligare två vymodeller:
- NoteViewModel.cs
- NotesViewModel.cs
Projektstrukturen bör se ut så här:
Om viewmodel- och About-vyn
I vyn Om visas vissa data på skärmen och du kan också navigera till en webbplats med mer information. Eftersom den här vyn inte har några data att ändra, till exempel med en textinmatningskontroll eller val av objekt från en lista, är det en bra kandidat att visa hur du lägger till en viewmodel. För About viewmodel finns det ingen stödmodell.
Skapa viewmodel för Om:
Dubbelklicka på ViewModels\AboutViewModel.cs i fönstret Solution Explorer i Visual Studio.
Klistra in följande kod:
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); }
Det tidigare kodfragmentet innehåller vissa egenskaper som representerar information om appen, till exempel namn och version. Det här kodfragmentet är exakt samma som om-modellen som du tog bort tidigare. Den här viewmodel innehåller dock ett nytt koncept, kommandoegenskapen ShowMoreInfoCommand .
Kommandon är bindbara åtgärder som anropar kod och är en bra plats för att placera applogik. I det här exemplet pekar ShowMoreInfoCommand på ShowMoreInfo metoden, som öppnar webbläsaren till en specifik sida. Du får lära dig mer om kommandosystemet i nästa avsnitt.
Om vy
Om-vyn måste ändras något för att koppla den till den vymodell som skapades i föregående avsnitt. Använd följande ändringar i filen Views\AboutPage.xaml :
-
xmlns:modelsUppdatera XML-namnområdet tillxmlns:viewModelsoch rikta in dig påNotes.ViewModels.NET-namnområdet. - Ändra egenskapen
ContentPage.BindingContexttill en ny instans avAbout-viewmodell. - Ta bort knappens
Clickedhändelsehanterare och använd egenskapenCommand.
Uppdatera vyn Om:
Dubbelklicka på Vyer\AboutPage.xaml i fönstret Solution Explorer i Visual Studio.
Klistra in följande kod:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:Notes.ViewModels" x:Class="Notes.Views.AboutPage" x:DataType="viewModels:AboutViewModel"> <ContentPage.BindingContext> <viewModels:AboutViewModel /> </ContentPage.BindingContext> <VerticalStackLayout Spacing="10" Margin="10"> <HorizontalStackLayout Spacing="10"> <Image Source="dotnet_bot.png" SemanticProperties.Description="The dot net bot waving hello!" HeightRequest="64" /> <Label FontSize="22" FontAttributes="Bold" Text="{Binding Title}" VerticalOptions="End" /> <Label FontSize="22" Text="{Binding Version}" VerticalOptions="End" /> </HorizontalStackLayout> <Label Text="{Binding Message}" /> <Button Text="Learn more..." Command="{Binding ShowMoreInfoCommand}" /> </VerticalStackLayout> </ContentPage>Det tidigare kodfragmentet markerar de rader som har ändrats i den här versionen av vyn.
Observera att knappen använder Command egenskapen . Många kontroller har en Command egenskap som anropas när användaren interagerar med kontrollen. När det används med en knapp anropas kommandot när en användare trycker på knappen, ungefär som händelsehanteraren Clicked anropas, förutom att du kan binda Command till en egenskap i viewmodel.
När användaren trycker på knappen i den här vyn, anropas Command.
Command är bunden till ShowMoreInfoCommand-egenskapen i en viewmodel, och när den ShowMoreInfo anropas, körs koden i metoden, vilket öppnar webbläsaren till en specifik sida.
Rensa om kod bakom
Knappen ShowMoreInfo använder inte händelsehanteraren, så LearnMore_Clicked koden bör tas bort från filen Views\AboutPage.xaml.cs . Ta bort koden, klassen ska bara innehålla konstruktorn:
Dubbelklicka på Vyer\AboutPage.xaml.cs i fönstret Solution Explorer i Visual Studio.
Tips/Råd
Du kan behöva expandera Views\AboutPage.xaml för att visa filen.
Ersätt koden med följande kodfragment:
namespace Notes.Views; public partial class AboutPage : ContentPage { public AboutPage() { InitializeComponent(); } }
Skapa anteckningsviewmodelen
Målet med att uppdatera anteckningsvyn är att flytta så mycket funktionalitet som möjligt från XAML-koden bakom och placera den i anteckningsvyn.
Anteckningsvymodell
Baserat på vad note-vyn kräver, måste note-vymodellen tillhandahålla följande objekt:
- Texten i anteckningen.
- Datum/tid då anteckningen skapades eller senast uppdaterades.
- Ett kommando som sparar anteckningen.
- Ett kommando som tar bort anteckningen.
Skapa Note viewmodel:
Dubbelklicka på ViewModels\NoteViewModel.cs i fönstret Solution Explorer i Visual Studio.
Ersätt koden i den här filen med följande kodfragment:
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.ComponentModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NoteViewModel : ObservableObject, IQueryAttributable { private Models.Note _note; }Den här koden är den tomma
Notevymodellen där du lägger till egenskaper och kommandon som stödjer vynNote. Observera attCommunityToolkit.Mvvm.ComponentModelnamnområdet importeras. Det här namnområdet innehåller denObservableObjectsom används som basklass. Du lär dig mer omObservableObjecti nästa steg. NamnområdetCommunityToolkit.Mvvm.Inputimporteras också. Det här namnområdet innehåller vissa kommandotyper som anropar metoder asynkront.Modellen
Models.Notelagras som ett privat fält. Egenskaperna och metoderna för den här klassen använder det här fältet.Lägg till följande egenskaper i klassen:
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;Egenskaperna
DateochIdentifierär enkla egenskaper som bara hämtar motsvarande värden från modellen.Tips/Råd
För egenskaper skapar syntaxen
=>en get-only-egenskap där -instruktionen till höger om=>måste utvärderas till ett värde som ska returneras.Egenskapen
Textkontrollerar först om värdet som anges är ett annat värde. Om värdet är annorlunda skickas det värdet vidare till modellens egenskap ochOnPropertyChangedmetoden anropas.Metoden
OnPropertyChangedtillhandahålls av basklassenObservableObject. Den här metoden använder namnet på den anropande koden, i det här fallet egenskapsnamnet text och genererarObservableObject.PropertyChangedhändelsen. Den här händelsen tillhandahåller namnet på egenskapen till alla händelseprenumeranter. Bindningssystemet som tillhandahålls av .NET MAUI identifierar den här händelsen och uppdaterar eventuella relaterade bindningar i användargränssnittet. När egenskapen ändras för note viewmodelTextaktiveras händelsen och alla gränssnittselement som är bundna tillTextegenskapen meddelas om att egenskapen har ändrats.Lägg till följande kommandoegenskaper i klassen, som är de kommandon som vyn kan binda till:
public ICommand SaveCommand { get; private set; } public ICommand DeleteCommand { get; private set; }Lägg till följande konstruktorer i klassen:
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); }Dessa två konstruktorer används antingen för att skapa viewmodel med en ny bakgrundsmodell, som är en tom anteckning, eller för att skapa en vymodell som använder den angivna modellinstansen.
Konstruktorerna konfigurerar också kommandona för viewmodel. Lägg sedan till koden för dessa kommandon.
Lägg till
SaveochDeletemetoderna: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}"); }Dessa metoder anropas av associerade kommandon. De utför relaterade åtgärder för modellen och gör att appen navigerar till föregående sida. En frågesträngsparameter läggs till i
..navigeringssökvägen, som anger vilken åtgärd som vidtogs och anteckningens unika identifierare.Lägg sedan till
ApplyQueryAttributesmetoden i klassen, som uppfyller kraven i IQueryAttributable gränssnittet:void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { if (query.ContainsKey("load")) { _note = Models.Note.Load(query["load"].ToString()); RefreshProperties(); } }När en sida, eller bindningskontexten för en sida, implementerar det här gränssnittet skickas de frågesträngsparametrar som används i navigeringen
ApplyQueryAttributestill metoden. Den här viewmodel används som bindningskontext för anteckningsvyn. När anteckningsvyn navigeras till, får vyns bindningskontext (vymodellen) de frågesträngsparametrar som användes under navigeringen.Den här koden kontrollerar om
loadnyckeln angavs iqueryordlistan. Om den här nyckeln hittas ska värdet vara identifieraren (filnamnet) för anteckningen som ska läsas in. Den anteckningen läses in och anges som det underliggande modellobjektet för den här viewmodel-instansen.Lägg slutligen till följande två hjälpmetoder i klassen:
public void Reload() { _note = Models.Note.Load(_note.Filename); RefreshProperties(); } private void RefreshProperties() { OnPropertyChanged(nameof(Text)); OnPropertyChanged(nameof(Date)); }Metoden
Reloadär en hjälpmetod som uppdaterar säkerhetskopieringsmodellobjektet och läser in det från enhetslagringen igenMetoden
RefreshPropertiesär en annan hjälpmetod för att säkerställa att alla prenumeranter som är bundna till det här objektet meddelas om attTextegenskaperna ochDatehar ändrats. Eftersom den underliggande modellen (fältet_note) ändras när anteckningen läses in under navigeringen, ställs inte egenskapernaTextochDatein på nya värden. Eftersom dessa egenskaper inte anges direkt meddelas inte bindningar som är kopplade till dessa egenskaper eftersomOnPropertyChangedde inte anropas för varje egenskap.RefreshPropertiessäkerställer att bindningar till dessa egenskaper uppdateras.
Koden för klassen bör se ut som följande kodfragment:
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));
}
}
Anteckningsvy
Nu när viewmodel har skapats uppdaterar du anteckningsvyn. Använd följande ändringar i filen Views\NotePage.xaml :
-
xmlns:viewModelsLägg till XML-namnområdet som är avsett förNotes.ViewModels.NET-namnområdet. - Lägg till en
BindingContextpå sidan. - Ta bort händelsehanterarna för knapparna för radera och spara
Clickedoch ersätt dem med kommandon.
Uppdatera anteckningsvyn:
I fönstret Solution Explorer i Visual Studio dubbelklickar du på Views\NotePage.xaml för att öppna XAML-redigeraren.
Klistra in följande kod:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:Notes.ViewModels" x:Class="Notes.Views.NotePage" Title="Note" x:DataType="viewModels:NoteViewModel"> <ContentPage.BindingContext> <viewModels:NoteViewModel /> </ContentPage.BindingContext> <VerticalStackLayout Spacing="10" Margin="5"> <Editor x:Name="TextEditor" Placeholder="Enter your note" Text="{Binding Text}" HeightRequest="100" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="4"> <Button Text="Save" Command="{Binding SaveCommand}"/> <Button Grid.Column="1" Text="Delete" Command="{Binding DeleteCommand}"/> </Grid> </VerticalStackLayout> </ContentPage>
Tidigare deklarerade den här vyn inte någon bindningskontext, eftersom den angavs av själva sidans kod bakom. Om du ställer in bindningskontexten direkt i XAML finns det två saker:
När sidan navigeras till vid körning visas en tom anteckning. Det beror på att den parameterlösa konstruktorn för bindningskontexten, viewmodel, anropas. Om du kommer ihåg rätt skapar den parameterlösa konstruktorn för note viewmodel en tom anteckning.
Intellisense i XAML-redigeraren visar de tillgängliga egenskaperna så snart du börjar skriva
{Bindingsyntax. Syntaxen verifieras också och aviserar dig om ett ogiltigt värde. Prova att ändra bindningssyntaxenSaveCommandför tillSave123Command. Om du hovrar musmarkören över texten ser du att en knappbeskrivning visas som informerar dig om att Save123Command inte hittas. Det här meddelandet betraktas inte som ett fel eftersom bindningar är dynamiska, det är verkligen en liten varning som kan hjälpa dig att märka när du skrev fel egenskap.Om du har ändrat SaveCommand till ett annat värde återställer du det nu.
Rensa anteckningskoden bakom
Nu när interaktionen med vyn har ändrats från händelsehanterare till kommandon öppnar du filen Views\NotePage.xaml.cs och ersätter all kod med en klass som bara innehåller konstruktorn:
Dubbelklicka på Vyer\NotePage.xaml.cs i fönstret Solution Explorer i Visual Studio.
Tips/Råd
Du kan behöva expandera Views\NotePage.xaml för att visa filen.
Ersätt koden med följande kodfragment:
namespace Notes.Views; public partial class NotePage : ContentPage { public NotePage() { InitializeComponent(); } }
Skapa vyn Anteckningar
Det sista viewmodel-view-paret är vyn Anteckningar viewmodel och AllNotes. För närvarande binds vyn dock direkt till modellen, som togs bort i början av den här handledningen. Målet med att uppdatera AllNotes-vyn är att flytta så mycket funktionalitet som möjligt från XAML-koden bakom och placera den i viewmodel. Återigen är fördelen att vyn kan ändra sin design med liten effekt på koden.
Visningsmodell för anteckningar
Baserat på vad AllNotes-vyn kommer att visa och vilka interaktioner användaren kommer att göra, måste notes viewmodel tillhandahålla följande objekt:
- En samling anteckningar.
- Ett kommando för att hantera navigering till en anteckning.
- Ett kommando för att skapa en ny anteckning.
- Uppdatera listan med anteckningar när en skapas, tas bort eller ändras.
Skapa Antecknings-vymodellen:
Dubbelklicka på ViewModels\NotesViewModel.cs i fönstret Solution Explorer i Visual Studio.
Ersätt koden i den här filen med följande kod:
using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NotesViewModel: IQueryAttributable { }Den här koden är tom
NotesViewModeldär du lägger till egenskaper och kommandon för att stödjaAllNotesvyn.NotesViewModelLägg till följande egenskaper i klasskoden:public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; } public ICommand NewCommand { get; } public ICommand SelectNoteCommand { get; }Egenskapen
AllNotesär enObservableCollectionsom lagrar alla anteckningar som läses in från enheten. De två kommandona används av vyn för att utlösa åtgärderna för att skapa en anteckning eller välja en befintlig anteckning.Lägg till en parameterlös konstruktor i klassen, som initierar kommandona och läser in anteckningarna från modellen:
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); }Observera att
AllNotessamlingen använderModels.Note.LoadAllmetoden för att fylla den observerbara samlingen med anteckningar. MetodenLoadAllreturnerar anteckningarnaModels.Notesom typ, men den observerbara samlingen är en samlingViewModels.NoteViewModeltyper. Koden använderSelectLinq-tillägget för att skapa viewmodel-instanser från anteckningsmodellerna som returneras frånLoadAll.Skapa de metoder som kommandona riktar in sig på:
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}"); }Observera att
NewNoteAsyncmetoden inte tar en parameter medan denSelectNoteAsyncgör det. Kommandon kan också ha en enda parameter som tillhandahålls när kommandot anropas. FörSelectNoteAsync-metoden representerar parametern noten som väljs.Implementera slutligen
IQueryAttributable.ApplyQueryAttributesmetoden: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))); } }Note viewmodel som skapades i det föregående steget av handledningen utnyttjade navigering när anteckningen sparades eller togs bort. Viewmodelle navigerade tillbaka till AllNotes-vyn som denna viewmodelle är associerad med. Den här koden identifierar om frågesträngen innehåller antingen
deletedeller-nyckelnsaved. Värdet för nyckeln är den unika identifieraren för anteckningen.Om anteckningen har tagits bort, matchas anteckningen mot den angivna identifieraren i
AllNotes-samlingen och tas bort.Det finns två möjliga orsaker till att en anteckning sparas. Anteckningen skapades eller så ändrades en befintlig anteckning. Om anteckningen redan finns i
AllNotessamlingen är det en anteckning som har uppdaterats. I det här fallet behöver anteckningsinstansen i samlingen bara uppdateras. Om anteckningen saknas i samlingen är det en ny anteckning och måste läggas till i samlingen.
Koden för klassen bör se ut som följande kodfragment:
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)));
}
}
}
Alla anteckningsvyn
Nu när viewmodel har skapats uppdaterar du AllNotes-vyn så att den pekar på egenskaperna för viewmodel. Använd följande ändringar i filen Views\AllNotesPage.xaml :
-
xmlns:viewModelsLägg till XML-namnområdet som är avsett förNotes.ViewModels.NET-namnområdet. - Lägg till en
BindingContextpå sidan. - Ta bort händelsen för verktygsfältsknappen
Clickedoch använd egenskapenCommand. - Ändra
CollectionViewför att binda dessItemSourcetillAllNotes. -
CollectionViewÄndra till att använda kommandon för att reagera på när det valda objektet ändras.
Uppdatera AllNotes-vyn:
Dubbelklicka på Views\AllNotesPage.xaml i fönstret Solution Explorer i Visual Studio.
Klistra in följande kod:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:Notes.ViewModels" x:Class="Notes.Views.AllNotesPage" Title="Your Notes" x:DataType="viewModels:NotesViewModel"> <ContentPage.BindingContext> <viewModels:NotesViewModel /> </ContentPage.BindingContext> <!-- Add an item to the toolbar --> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Command="{Binding NewCommand}" IconImageSource="{FontImageSource Glyph='+', Color=Black, Size=22}" /> </ContentPage.ToolbarItems> <!-- Display notes in a list --> <CollectionView x:Name="notesCollection" ItemsSource="{Binding AllNotes}" Margin="20" SelectionMode="Single" SelectionChangedCommand="{Binding SelectNoteCommand}" SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}"> <!-- Designate how the collection of items are laid out --> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Vertical" ItemSpacing="10" /> </CollectionView.ItemsLayout> <!-- Define the appearance of each item in the list --> <CollectionView.ItemTemplate> <DataTemplate x:DataType="viewModels:NoteViewModel"> <StackLayout> <Label Text="{Binding Text}" FontSize="22"/> <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </ContentPage>
Verktygsfältet använder Clicked inte längre händelsen och använder i stället ett kommando.
CollectionView stödjer kommandohantering med egenskaperna SelectionChangedCommand och SelectionChangedCommandParameter. I den uppdaterade XAML-filen SelectionChangedCommand är egenskapen bunden till viewmodels SelectNoteCommand, vilket innebär att kommandot anropas när det valda objektet ändras. När kommandot anropas skickas egenskapsvärdet SelectionChangedCommandParameter till kommandot.
Titta på bindningen som används för CollectionView:
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding AllNotes}"
Margin="20"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectNoteCommand}"
SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}">
Egenskapen SelectionChangedCommandParameter använder Source={RelativeSource Self} bindning. Refererar Self till det aktuella objektet, som är CollectionView. Anger x:DataTypeCollectionView därför som typ för den kompilerade bindningen. Observera att bindningsvägen är egenskapen SelectedItem. När kommandot anropas genom att det valda objektet SelectNoteCommand ändras anropas kommandot och det markerade objektet skickas till kommandot som en parameter.
För det bindningsuttryck som definierats i SelectionChangedCommandParameter egenskapen som ska kompileras är det nödvändigt att instruera projektet att aktivera kompilerade bindningar i uttryck som anger Source egenskapen. Om du vill göra detta redigerar du projektfilen för din lösning och lägger till <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> i elementet <PropertyGroup> :
<PropertyGroup>
<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
</PropertyGroup>
Rensa AllNotes-koden bakom
Nu när interaktionen med vyn har ändrats från händelsehanterare till kommandon öppnar du filen Views\AllNotesPage.xaml.cs och ersätter all kod med en klass som bara innehåller konstruktorn:
I fönstret Solution Explorer i Visual Studio dubbelklickar du på Vyer\AllNotesPage.xaml.cs.
Tips/Råd
Du kan behöva expandera Views\AllNotesPage.xaml för att visa filen.
Ersätt koden med följande kodfragment:
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); } }
Kör appen
Nu kan du köra appen och allt fungerar. Det finns dock två problem med hur appen beter sig:
- Om du väljer en anteckning, som öppnar redigeraren, trycker du på Spara och sedan försöker välja samma anteckning fungerar den inte.
- När en anteckning ändras eller läggs till ordnas inte listan med anteckningar om för att visa de senaste anteckningarna högst upp.
De här två problemen åtgärdas i nästa självstudiesteg.
Åtgärda appbeteendet
Nu när appkoden kan kompileras och köras har du förmodligen märkt att det finns två fel med hur appen beter sig. Appen låter dig inte markera en anteckning som redan är markerad och listan med anteckningar ordnas inte om när en anteckning har skapats eller ändrats.
Flytta anteckningar överst i listan
Åtgärda först omordningsproblemet med anteckningslistan. I filen AllNotes innehåller samlingen alla anteckningar som ska visas för användaren. Tyvärr är nackdelen med att använda en ObservableCollection att den måste sorteras manuellt. Utför följande steg för att hämta de nya eller uppdaterade objekten överst i listan:
Dubbelklicka på ViewModels\NotesViewModel.cs i fönstret Solution Explorer i Visual Studio.
ApplyQueryAttributesI metoden tittar du på logiken för den sparade frågesträngsnyckeln.När
matchedNoteintenull, uppdateras anteckningen.AllNotes.MoveAnvänd metoden för att flyttamatchedNotetill index 0, som är överst i listan.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); }Metoden
AllNotes.Movetar två parametrar för att flytta ett objekts position i samlingen. Den första parametern är indexet för det objekt som ska flyttas, och den andra parametrarna är indexet för var objektet ska flyttas. MetodenAllNotes.IndexOfhämtar anteckningens index.matchedNoteNärnullär, är anteckningen ny och läggs till i listan. I stället för att lägga till den, som lägger till anteckningen i slutet av listan, infogar du anteckningen vid index 0, som är överst i listan.AllNotes.AddÄndra metoden tillAllNotes.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)));
Metoden ApplyQueryAttributes bör se ut som följande kodfragment:
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)));
}
}
Tillåt att du väljer en anteckning två gånger
I vyn CollectionView visas alla anteckningar, men du kan inte välja samma anteckning två gånger. Det finns två sätt som objektet förblir valt: när användaren ändrar en befintlig anteckning och när användaren tvingas navigera bakåt. Fallet där användaren sparar en anteckning korrigeras med kodändringen i föregående avsnitt som använder AllNotes.Move, så du behöver inte bekymra dig om det fallet.
Problemet som du måste lösa nu är relaterat till navigering. Oavsett hur man navigerar till Allnotes-vyn så genereras händelsen för sidan. Den här händelsen är ett perfekt tillfälle att tvångsavmarkera det markerade objektet i CollectionView.
Men med MVVM-mönstret som tillämpas här kan viewmodel inte utlösa något direkt i vyn, till exempel rensa det valda objektet när anteckningen har sparats. Så hur får man det att hända? En bra implementering av MVVM-mönstret minimerar bakomliggande kod i vyn. Det finns några olika sätt att lösa det här problemet för att stödja MVVM-separationsmönstret. Men det är också OK att placera kod i code-behind för vyn, särskilt när den är direkt kopplad till vyn. MVVM har många bra design och koncept som hjälper dig att dela upp din app, förbättra underhållsbarheten och göra det enklare för dig att lägga till nya funktioner. I vissa fall kan det dock hända att MVVM uppmuntrar till överengineering.
Överarbeta inte lösningen för det här problemet och använd helt enkelt NavigatedTo-händelsen för att ta bort det valda objektet från CollectionView.
Dubbelklicka på Views\AllNotesPage.xaml i fönstret Solution Explorer i Visual Studio.
I XAML för
<ContentPage>lägger du tillNavigatedTohändelsen:<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModels="clr-namespace:Notes.ViewModels" x:Class="Notes.Views.AllNotesPage" Title="Your Notes" NavigatedTo="ContentPage_NavigatedTo" x:DataType="viewModels:NotesViewModel"> <ContentPage.BindingContext> <viewModels:NotesViewModel />Du kan lägga till en standardhändelsehanterare genom att högerklicka på händelsemetodens namn
ContentPage_NavigatedTooch välja Gå till definition. Den här åtgärden öppnar views\AllNotesPage.xaml.cs i kodredigeraren.Ersätt händelsehanterarkoden med följande kodfragment:
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { notesCollection.SelectedItem = null; }I XAML gavs
CollectionViewnamnetnotesCollection. Den här koden använder det namnet för att komma åtCollectionViewoch angeSelectedItemtillnull. Det markerade objektet rensas varje gång sidan navigeras till.
Kör nu appen. Försök att navigera till en anteckning, tryck på bakåtknappen och välj samma anteckning en andra gång. Appbeteendet är åtgärdat!
Utforska koden för den här självstudien.. Om du vill ladda ned en kopia av det slutförda projektet för att jämföra koden med laddar du ned det här projektet.
Din app använder nu MVVM-mönster!
Nästa steg
Följande länkar innehåller mer information om några av de begrepp som du har lärt dig i den här självstudien: