Upgrade aplikace pomocí konceptů MVVM
Tato série kurzů je navržená tak, aby pokračovala v kurzu Vytvoření aplikace .NET MAUI, která vytvořila aplikaci pro pořizování poznámek. V této části série se naučíte:
- Implementujte model model-view-viewmodel (MVVM).
- Pro předávání dat během navigace použijte další styl řetězce dotazu.
Důrazně doporučujeme, abyste nejprve postupli podle kurzu Vytvoření aplikace .NET MAUI, protože kód vytvořený v tomto kurzu je základem tohoto kurzu. Pokud jste kód ztratili nebo chcete začít znovu, stáhněte si tento projekt.
Principy MVVM
Vývojářské prostředí .NET MAUI obvykle zahrnuje vytvoření uživatelského rozhraní v jazyce XAML a následné přidání kódu, který funguje v uživatelském rozhraní. Složité problémy s údržbou můžou nastat, když se aplikace upraví a zvětšují jejich velikost a rozsah. Mezi tyto problémy patří úzká párování mezi ovládacími prvky uživatelského rozhraní a obchodní logikou, což zvyšuje náklady na úpravy uživatelského rozhraní a potíže s testováním jednotek takového kódu.
Model-viewmodel (MVVM) pomáhá čistě oddělit obchodní a prezentační logiku aplikace od uživatelského rozhraní (UI). Udržování čistého oddělení mezi logikou aplikace a uživatelským rozhraním pomáhá řešit řadu problémů s vývojem a usnadňuje testování, údržbu a vývoj aplikací. Může také výrazně zlepšit možnosti opětovného použití kódu a umožní vývojářům a návrhářům uživatelského rozhraní snadněji spolupracovat při vývoji příslušných částí aplikace.
Vzor
Model MVVM obsahuje tři základní komponenty: model, zobrazení a model zobrazení. Každý z nich slouží k odlišnému účelu. Následující diagram znázorňuje vztahy mezi třemi komponentami.
Kromě porozumění zodpovědnostem jednotlivých komponent je také důležité pochopit, jak komunikují. Na vysoké úrovni zobrazení "zná" model zobrazení a model zobrazení "ví o" modelu, ale model zobrazení nezná model zobrazení a model zobrazení toto zobrazení nezná. Model zobrazení proto izoluje zobrazení od modelu a umožňuje, aby se model vyvinul nezávisle na zobrazení.
Klíčem k efektivnímu používání virtuálního počítače MVVM je pochopení, jak začlenit kód aplikace do správných tříd a jak třídy interagují.
Zobrazení
Zobrazení zodpovídá za definování struktury, rozložení a vzhledu toho, co uživatel vidí na obrazovce. V ideálním případě je každé zobrazení definováno v XAML s omezeným kódem, který neobsahuje obchodní logiku. V některých případech ale kód na pozadí může obsahovat logiku uživatelského rozhraní, která implementuje vizuální chování, které je obtížné vyjádřit v JAZYCE XAML, například animace.
ViewModel
Model zobrazení implementuje vlastnosti a příkazy, ke kterým může zobrazení vytvořit vazbu dat, a upozorní zobrazení změn stavu prostřednictvím událostí oznámení o změnách. Vlastnosti a příkazy, které model zobrazení poskytuje, definují funkce, které má uživatelské rozhraní nabídnout, ale zobrazení určuje, jak se má tato funkce zobrazit.
Model zobrazení je také zodpovědný za koordinaci interakcí zobrazení se všemi třídami modelu, které jsou požadovány. Mezi modelem zobrazení a třídami modelu modelu je obvykle relace 1:N.
Každý model zobrazení poskytuje data z modelu ve formuláři, který může zobrazení snadno využívat. K tomu model zobrazení někdy provádí převod dat. Umístění tohoto převodu dat do modelu zobrazení je vhodné, protože poskytuje vlastnosti, se kterými může zobrazení vytvořit vazbu. Model zobrazení může například zkombinovat hodnoty dvou vlastností, aby bylo snazší zobrazení zobrazit.
Důležité
.NET MAUI zařazuje aktualizace vazby do vlákna uživatelského rozhraní. Při použití MVVM to umožňuje aktualizovat vlastnosti modelu zobrazení vázaného na data z libovolného vlákna pomocí vazbového modulu .NET MAUI, který přináší aktualizace do vlákna uživatelského rozhraní.
Model
Třídy modelu jsou ne vizuálové třídy, které zapouzdřují data aplikace. Model si proto můžete představit jako reprezentaci doménového modelu aplikace, který obvykle zahrnuje datový model spolu s obchodní a ověřovací logikou.
Aktualizace modelu
V této první části kurzu implementujete model-view-viewmodel (MVVM). Začněte tím, že otevřete řešení Notes.sln v sadě Visual Studio.
Vyčištění modelu
V předchozím kurzu se typy modelů chovaly jako model (data) i jako model zobrazení (příprava dat), který byl namapován přímo na zobrazení. Následující tabulka popisuje model:
Soubor kódu | Popis |
---|---|
Modely/About.cs | Model About . Obsahuje pole jen pro čtení, která popisují samotnou aplikaci, například název a verzi aplikace. |
Modely/Note.cs | Model Note . Představuje poznámku. |
Modely/AllNotes.cs | Model AllNotes . Načte všechny poznámky na zařízení do kolekce. |
Když uvažujete o samotné aplikaci, existuje pouze jedna část dat, která aplikace používá .Note
Poznámky se načtou ze zařízení, uloží se do zařízení a upraví se prostřednictvím uživatelského rozhraní aplikace. Pro modely AllNotes
a modely to opravdu není potřebaAbout
. Odeberte z projektu tyto modely:
- Najděte podokno Průzkumník řešení sady Visual Studio.
- Klikněte pravým tlačítkem myši na soubor Models\About.cs a vyberte Odstranit. Stisknutím klávesy OK odstraňte soubor.
- Klikněte pravým tlačítkem myši na soubor Models\AllNotes.cs a vyberte Odstranit. Stisknutím klávesy OK odstraňte soubor.
Jediným zbývajícím souborem modelu je soubor Models\Note.cs .
Aktualizace modelu
Model Note
obsahuje:
- Jedinečný identifikátor, což je název souboru poznámky uložený v zařízení.
- Text poznámky.
- Datum označující, kdy byla poznámka vytvořena nebo kdy byla naposledy aktualizována.
V současné době se načítání a ukládání modelu provádělo prostřednictvím zobrazení a v některých případech i jinými typy modelů, které jste právě odebrali. Kód, Note
který máte pro tento typ, by měl být následující:
namespace Notes.Models;
internal class Note
{
public string Filename { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
Model Note
se rozšíří, aby zvládl načítání, ukládání a odstraňování poznámek.
V podokně Průzkumník řešení sady Visual Studio poklikejte na Models\Note.cs.
V editoru kódu přidejte do třídy následující dvě metody
Note
. Tyto metody jsou založené na instancích a zpracovávají ukládání nebo odstraňování aktuální poznámky do nebo ze zařízení, v uvedeném pořadí: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));
Aplikace musí načítat poznámky dvěma způsoby, načítat jednotlivé poznámky ze souboru a načítat všechny poznámky do zařízení. Kód pro zpracování načítání může být
static
členy a nevyžaduje spuštění instance třídy.Přidejte do třídy následující kód, který načte poznámku podle názvu souboru:
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) }; }
Tento kód převezme název souboru jako parametr, vytvoří cestu k umístění, kde jsou poznámky uložené v zařízení, a pokusí se načíst soubor, pokud existuje.
Druhým způsobem, jak načíst poznámky, je vytvořit výčet všech poznámek v zařízení a načíst je do kolekce.
Do třídy přidejte následující kód:
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); }
Tento kód vrátí výčet typů
Note
modelů načtením souborů na zařízení, které odpovídají vzoru souboru poznámek: *.notes.txt. Každý název souboru se předáLoad
metodě a načítá jednotlivé poznámky. Nakonec se kolekce poznámek řadí podle data každé poznámky a vrátí se volajícímu.Nakonec do třídy přidejte konstruktor, který nastaví výchozí hodnoty vlastností, včetně náhodného názvu souboru:
public Note() { Filename = $"{Path.GetRandomFileName()}.notes.txt"; Date = DateTime.Now; Text = ""; }
Kód Note
třídy by měl vypadat takto:
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);
}
}
Teď, když Note
je model dokončený, je možné modely zobrazení vytvořit.
Vytvoření modelu O zobrazení
Před přidáním modelů zobrazení do projektu přidejte odkaz na MVVM Community Toolkit. Tato knihovna je k dispozici na NuGetu a poskytuje typy a systémy, které pomáhají implementovat model MVVM.
V podokně Průzkumník řešení sady Visual Studio klikněte pravým tlačítkem na projekt >Poznámky Spravovat balíčky NuGet.
Vyberte kartu Procházet.
Vyhledejte communitytoolkit mvvm a vyberte
CommunityToolkit.Mvvm
balíček, který by měl být prvním výsledkem.Ujistěte se, že je vybraná aspoň verze 8. Tento kurz byl napsán pomocí verze 8.0.0.
Dále vyberte Nainstalovat a přijměte všechny zobrazené výzvy.
Teď jste připraveni začít aktualizovat projekt přidáním modelů zobrazení.
Oddělení s modely zobrazení
Relace modelu view-to-viewmodel spoléhá silně na systém vazeb poskytovaný víceplatformním uživatelským rozhraním aplikace .NET (.NET MAUI). Aplikace už v zobrazení používá vazbu k zobrazení seznamu poznámek a k zobrazení textu a data jedné poznámky. Logika aplikace je aktuálně poskytována kódem zobrazení a je přímo svázaná se zobrazením. Když například uživatel upravuje poznámku a stiskne tlačítko Uložit , Clicked
vyvolá se událost tlačítka. Potom kód obslužné rutiny události uloží text poznámky do souboru a přejde na předchozí obrazovku.
Když se zobrazení změní, může se logika aplikace v kódu zobrazení stát problémem. Pokud je například tlačítko nahrazeno jiným vstupním ovládacím prvku nebo se změní název ovládacího prvku, můžou být obslužné rutiny událostí neplatné. Bez ohledu na to, jak je zobrazení navržené, je účelem zobrazení vyvolat určitou logiku aplikace a prezentovat informace uživateli. U této aplikace Save
tlačítko ukládá poznámku a pak přejde zpět na předchozí obrazovku.
Model viewmodel dává aplikaci konkrétní místo pro vložení logiky aplikace bez ohledu na to, jak je uživatelské rozhraní navržené nebo jak se data načítají nebo ukládají. Model viewmodel je lepidlo, které představuje datový model a pracuje s ním jménem zobrazení.
Modely zobrazení jsou uložené ve složce ViewModels .
- Najděte podokno Průzkumník řešení sady Visual Studio.
- Klikněte pravým tlačítkem myši na projekt Poznámky a vyberte Přidat>novou složku. Pojmenujte složku ViewModels.
- Klikněte pravým tlačítkem myši na složku> ViewModels Přidat>třídu a pojmenujte ji AboutViewModel.cs.
- Opakujte předchozí krok a vytvořte dva další modely zobrazení:
- NoteViewModel.cs
- NotesViewModel.cs
Struktura projektu by měla vypadat jako na následujícím obrázku:
O modelu viewmodel a o zobrazení
Zobrazení O aplikaci zobrazí některá data na obrazovce a volitelně přejde na web s dalšími informacemi. Vzhledem k tomu, že toto zobrazení neobsahuje žádná data, která by se měla změnit, například u ovládacího prvku pro zadávání textu nebo výběru položek ze seznamu, je vhodným kandidátem na přidání modelu zobrazení. Pro model About viewmodel neexistuje záložní model.
Vytvořte model About viewmodel:
V podokně Průzkumník řešení sady Visual Studio poklikejte na ViewModels\AboutViewModel.cs.
Vložte následující kód:
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); }
Předchozí fragment kódu obsahuje některé vlastnosti, které představují informace o aplikaci, jako je název a verze. Tento fragment kódu je úplně stejný jako model About, který jste odstranili dříve. Tento model viewmodel však obsahuje nový koncept, ShowMoreInfoCommand
vlastnost příkazu.
Příkazy jsou akce s možností vazby, které volají kód, a jsou skvělým místem k vložení logiky aplikace. V tomto příkladu ShowMoreInfoCommand
odkazuje na metodu ShowMoreInfo
, která otevře webový prohlížeč na konkrétní stránku. Další informace o příkazovém systému najdete v další části.
O zobrazení
Zobrazení O aplikaci je potřeba změnit mírně, aby bylo možné ho připojit k modelu zobrazení vytvořenému v předchozí části. V souboru Views\AboutPage.xaml použijte následující změny:
- Aktualizujte
xmlns:models
obor názvů XML tak, abyxmlns:viewModels
cílil naNotes.ViewModels
obor názvů .NET. ContentPage.BindingContext
Změňte vlastnost na novou instanciAbout
modelu viewmodel.- Odeberte obslužnou rutinu
Clicked
události tlačítka a použijteCommand
vlastnost.
Aktualizujte zobrazení O aplikaci:
V podokně Průzkumník řešení sady Visual Studio poklikejte na Views\AboutPage.xaml.
Vložte následující kód:
<?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>
Předchozí fragment kódu zvýrazní řádky, které se v této verzi zobrazení změnily.
Všimněte si, že tlačítko používá Command
vlastnost. Mnoho ovládacích prvků má Command
vlastnost, která se vyvolá, když uživatel pracuje s ovládacím prvku. Při použití s tlačítkem se příkaz vyvolá, když uživatel stiskne tlačítko, podobně jako Clicked
je vyvolána obslužná rutina události, s výjimkou toho, že můžete vytvořit vazbu Command
na vlastnost v modelu viewmodel.
Když uživatel v tomto zobrazení stiskne tlačítko, Command
vyvolá se. Je Command
vázán na ShowMoreInfoCommand
vlastnost v modelu viewmodel a při vyvolání spustí kód v ShowMoreInfo
metodě, který otevře webový prohlížeč na konkrétní stránku.
Vyčištění kódu o kódu
Tlačítko ShowMoreInfo
nepoužívá obslužnou rutinu události, takže LearnMore_Clicked
kód by měl být odebrán ze souboru Views\AboutPage.xaml.cs . Odstraňte tento kód, třída by měla obsahovat pouze konstruktor:
V podokně Průzkumník řešení sady Visual Studio poklikejte na Zobrazení\AboutPage.xaml.cs.
Tip
Možná budete muset rozbalit Zobrazení\AboutPage.xaml , aby se soubor zobrazil.
Nahraďte kód následujícím fragmentem kódu:
namespace Notes.Views; public partial class AboutPage : ContentPage { public AboutPage() { InitializeComponent(); } }
Vytvoření modelu zobrazení poznámky
Cílem aktualizace zobrazení Poznámky je přesunout co nejvíce funkcí z kódu XAML a umístit ho do modelu zobrazení Poznámky.
Model zobrazení poznámek
Na základě toho, co zobrazení Poznámky vyžaduje, musí model viewmodel poznámky poskytnout následující položky:
- Text poznámky.
- Datum a čas vytvoření poznámky nebo poslední aktualizace.
- Příkaz, který uloží poznámku.
- Příkaz, který poznámku odstraní.
Vytvořte model Zobrazení poznámek:
V podokně Průzkumník řešení sady Visual Studio poklikejte na ViewModels\NoteViewModel.cs.
Nahraďte kód v tomto souboru následujícím fragmentem kódu:
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.ComponentModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NoteViewModel : ObservableObject, IQueryAttributable { private Models.Note _note; }
Tento kód je prázdný
Note
model zobrazení, ve kterém přidáte vlastnosti a příkazy pro podporuNote
zobrazení. Všimněte si, že seCommunityToolkit.Mvvm.ComponentModel
obor názvů importuje. Tento obor názvů poskytujeObservableObject
použitou jako základní třídu. DalšíObservableObject
informace najdete v dalším kroku. OborCommunityToolkit.Mvvm.Input
názvů je také importován. Tento obor názvů poskytuje některé typy příkazů, které volají metody asynchronně.Model
Models.Note
se ukládá jako soukromé pole. Vlastnosti a metody této třídy budou používat toto pole.Do třídy přidejte následující vlastnosti:
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;
Vlastnosti
Date
jsouIdentifier
jednoduché vlastnosti, které pouze načítají odpovídající hodnoty z modelu.Tip
U vlastností syntaxe vytvoří vlastnost get-only,
=>
kde příkaz napravo od=>
musí vyhodnotit hodnotu, která se má vrátit.Vlastnost
Text
nejprve zkontroluje, jestli je nastavená hodnota jinou hodnotou. Pokud se hodnota liší, tato hodnota se předá vlastnosti modelu aOnPropertyChanged
volá se metoda.Metoda
OnPropertyChanged
je poskytovánaObservableObject
základní třídou. Tato metoda používá název volajícího kódu, v tomto případě název vlastnosti Text a vyvoláObservableObject.PropertyChanged
událost. Tato událost poskytuje název vlastnosti všem odběratelům události. Systém vazeb poskytovaný rozhraním .NET MAUI tuto událost rozpozná a aktualizuje všechny související vazby v uživatelském rozhraní. U modelu Note viewmodel přiText
změně vlastnosti je vyvolána událost a jakýkoli prvek uživatelského rozhraní, který je vázán naText
vlastnost, je upozorněn, že se vlastnost změnila.Do třídy přidejte následující vlastnosti příkazů, což jsou příkazy, ke kterým může zobrazení vytvořit vazbu:
public ICommand SaveCommand { get; private set; } public ICommand DeleteCommand { get; private set; }
Do třídy přidejte následující konstruktory:
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); }
Tyto dva konstruktory se používají k vytvoření modelu viewmodel s novým modelem pro pozadí, což je prázdná poznámka, nebo k vytvoření modelu zobrazení, který používá zadanou instanci modelu.
Konstruktory také nastaví příkazy pro model viewmodel. Dále přidejte kód pro tyto příkazy.
Přidejte metody
Save
aDelete
metody: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}"); }
Tyto metody jsou vyvolány přidruženými příkazy. Provádějí související akce v modelu a přejdou aplikaci na předchozí stránku. Do navigační cesty se přidá
..
parametr řetězce dotazu, který označuje, která akce byla provedena, a jedinečný identifikátor poznámky.Dále přidejte metodu
ApplyQueryAttributes
do třídy, která splňuje požadavky IQueryAttributable rozhraní:void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { if (query.ContainsKey("load")) { _note = Models.Note.Load(query["load"].ToString()); RefreshProperties(); } }
Když stránka nebo kontext vazby stránky implementuje toto rozhraní, předají se
ApplyQueryAttributes
metodě parametry řetězce dotazu použité v navigaci. Tento model zobrazení se používá jako kontext vazby pro zobrazení Poznámky. Při přechodu do zobrazení Poznámky se kontext vazby zobrazení (tento model zobrazení) předává parametry řetězce dotazu použité během navigace.Tento kód zkontroluje, jestli
load
byl klíč zadaný ve slovníkuquery
. Pokud se tento klíč najde, měla by být hodnota identifikátorem (název souboru) poznámky, kterou chcete načíst. Tato poznámka se načte a nastaví jako základní objekt modelu této instance modelu viewmodel.Nakonec do třídy přidejte tyto dvě pomocné metody:
public void Reload() { _note = Models.Note.Load(_note.Filename); RefreshProperties(); } private void RefreshProperties() { OnPropertyChanged(nameof(Text)); OnPropertyChanged(nameof(Date)); }
Metoda
Reload
je pomocná metoda, která aktualizuje objekt modelu backingu a znovu ho načte z úložiště zařízení.Tato
RefreshProperties
metoda je další pomocná metoda, která zajistí, že všichni odběratelé vázané na tento objekt budou upozorněni, že se změnilyText
vlastnosti.Date
Vzhledem k tomu,Text
že se při načtení poznámky během navigace změní základní model (_note
pole), nejsou vlastnostiDate
ve skutečnosti nastaveny na nové hodnoty. Vzhledem k tomu, že tyto vlastnosti nejsou přímo nastaveny, žádné vazby připojené k těmto vlastnostem by se neoznamily, protožeOnPropertyChanged
nejsou volány pro každou vlastnost.RefreshProperties
zajišťuje, že se vazby na tyto vlastnosti aktualizují.
Kód pro třídu by měl vypadat jako následující fragment kódu:
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));
}
}
Zobrazení poznámek
Teď, když je model viewmodel vytvořen, aktualizujte zobrazení Poznámky. V souboru Views\NotePage.xaml použijte následující změny:
xmlns:viewModels
Přidejte obor názvů XML, který cílí naNotes.ViewModels
obor názvů .NET.BindingContext
Přidejte na stránku stránku.- Odstraňte a uložte obslužné rutiny událostí tlačítka
Clicked
a nahraďte je příkazy.
Aktualizujte zobrazení poznámek:
- V podokně Průzkumník řešení sady Visual Studio poklikejte na Views\NotePage.xaml a otevřete editor XAML.
- Vložte následující kód:
<?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>
Dříve toto zobrazení nehlásilo kontext vazby, protože bylo dodáno kódem samotné stránky. Nastavení kontextu vazby přímo v XAML nabízí dvě věci:
Při spuštění se při přechodu na stránku zobrazí prázdná poznámka. Důvodem je to, že konstruktor bez parametrů pro kontext vazby, viewmodel, je vyvolán. Pokud si pamatujete správně, konstruktor bez parametrů pro model viewmodel poznámky vytvoří prázdnou poznámku.
IntelliSense v editoru XAML zobrazí dostupné vlastnosti hned, jak začnete psát
{Binding
syntaxi. Syntaxe se také ověří a upozorní vás na neplatnou hodnotu. Zkuste změnit syntaxi vazbySaveCommand
Save123Command
na . Pokud na text najedete myší, všimnete si, že se zobrazí popis s informacemi o tom, že Save123Command nebyl nalezen. Toto oznámení se nepovažuje za chybu, protože vazby jsou dynamické, je to opravdu malé upozornění, které vám může pomoct při zadávání nesprávné vlastnosti.Pokud jste změnili saveCommand na jinou hodnotu, obnovte ji nyní.
Vyčištění kódu poznámky
Teď, když se interakce se zobrazením změnila z obslužných rutin událostí na příkazy, otevřete soubor Views\NotePage.xaml.cs a nahraďte veškerý kód třídou, která obsahuje pouze konstruktor:
V podokně Průzkumník řešení sady Visual Studio poklikejte na Zobrazení\NotePage.xaml.cs.
Tip
Abyste mohli soubor zobrazit, budete možná muset rozbalit zobrazení\NotePage.xaml .
Nahraďte kód následujícím fragmentem kódu:
namespace Notes.Views; public partial class NotePage : ContentPage { public NotePage() { InitializeComponent(); } }
Vytvoření modelu zobrazení poznámek
Poslední dvojicí viewmodel-view je zobrazení Poznámky a zobrazení AllNotes. V současné době je ale zobrazení vázáno přímo na model, který byl odstraněn na začátku tohoto kurzu. Cílem při aktualizaci zobrazení AllNotes je přesunout co nejvíce funkcí z kódu XAML a umístit ho do modelu viewmodel. Opět platí, že zobrazení může změnit jeho návrh s malým efektem na váš kód.
Model zobrazení poznámek
Na základě toho, co bude zobrazení AllNotes zobrazovat a jaké interakce bude uživatel dělat, musí model zobrazení Poznámek obsahovat následující položky:
- Kolekce poznámek.
- Příkaz pro zpracování navigace na poznámku.
- Příkaz pro vytvoření nové poznámky.
- Aktualizujte seznam poznámek při vytváření, odstranění nebo změně.
Vytvořte model zobrazení poznámek:
V podokně Průzkumník řešení sady Visual Studio poklikejte na ViewModels\NotesViewModel.cs.
Nahraďte kód v tomto souboru následujícím kódem:
using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NotesViewModel: IQueryAttributable { }
Tento kód je prázdný
NotesViewModel
, kde přidáte vlastnosti a příkazy pro podporuAllNotes
zobrazení.NotesViewModel
Do kódu třídy přidejte následující vlastnosti:public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; } public ICommand NewCommand { get; } public ICommand SelectNoteCommand { get; }
Vlastnost
AllNotes
je,ObservableCollection
která ukládá všechny poznámky načtené ze zařízení. Tyto dva příkazy se použijí v zobrazení k aktivaci akcí vytvoření poznámky nebo výběru existující poznámky.Přidejte do třídy konstruktor bez parametrů, který inicializuje příkazy a načte poznámky z modelu:
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); }
Všimněte si, že
AllNotes
kolekce používá metoduModels.Note.LoadAll
k vyplnění pozorovatelné kolekce poznámkami. MetodaLoadAll
vrátí poznámky jakoModels.Note
typ, ale pozorovatelná kolekce je kolekce typůViewModels.NoteViewModel
. Kód používáSelect
rozšíření Linq k vytvoření instancí modelu viewmodel z modelů poznámek vrácených zLoadAll
.Vytvořte metody cílené příkazy:
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}"); }
Všimněte si, že
NewNoteAsync
metoda nezabírají parametr, když anoSelectNoteAsync
. Příkazy můžou mít volitelně jeden parametr, který je k dispozici při vyvolání příkazu. Pro metoduSelectNoteAsync
představuje parametr vybranou poznámku.Nakonec implementujte metodu
IQueryAttributable.ApplyQueryAttributes
: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))); } }
Model viewmodel poznámky vytvořený v předchozím kroku kurzu se používá k navigaci při uložení nebo odstranění poznámky. Model viewmodel se vrátil do zobrazení AllNotes, ke kterému je tento model zobrazení přidružený. Tento kód zjistí, jestli řetězec dotazu obsahuje
deleted
klíč nebosaved
klíč. Hodnota klíče je jedinečný identifikátor poznámky.Pokud byla poznámka odstraněna, je tato poznámka v kolekci spárována
AllNotes
zadaným identifikátorem a odebrána.Poznámku si uložíte ze dvou možných důvodů. Poznámka se buď právě vytvořila, nebo se změnila existující poznámka. Pokud už je poznámka v kolekci
AllNotes
, je to poznámka, která se aktualizovala. V takovém případě je potřeba aktualizovat instanci poznámky v kolekci. Pokud v kolekci chybí poznámka, jedná se o novou poznámku, která se musí přidat do kolekce.
Kód pro třídu by měl vypadat jako následující fragment kódu:
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)));
}
}
}
Zobrazení AllNotes
Teď, když byl model viewmodel vytvořen, aktualizujte zobrazení AllNotes tak, aby odkazovalo na vlastnosti modelu viewmodel. V souboru Views\AllNotesPage.xaml použijte následující změny:
xmlns:viewModels
Přidejte obor názvů XML, který cílí naNotes.ViewModels
obor názvů .NET.BindingContext
Přidejte na stránku stránku.- Odeberte událost tlačítka
Clicked
panelu nástrojů a použijteCommand
vlastnost. CollectionView
Změňte vazbu na vazbu sAllNotes
ItemSource
.CollectionView
Změňte příkazy pro reakci na změny vybrané položky.
Aktualizujte zobrazení AllNotes:
V podokně Průzkumník řešení sady Visual Studio poklikejte na Views\AllNotesPage.xaml.
Vložte následující kód:
<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>
Panel nástrojů už událost nepoužívá Clicked
a místo toho používá příkaz.
Podporuje CollectionView
příkazování s vlastnostmi SelectionChangedCommand
a SelectionChangedCommandParameter
vlastnostmi. V aktualizovaném KÓDU XAML je vlastnost vázána na model viewmodel SelectNoteCommand
, což znamená, SelectionChangedCommand
že příkaz je vyvolán při změně vybrané položky. Při vyvolání SelectionChangedCommandParameter
příkazu se do příkazu předá hodnota vlastnosti.
Podívejte se na vazbu použitou CollectionView
pro:
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding AllNotes}"
Margin="20"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectNoteCommand}"
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
Vlastnost SelectionChangedCommandParameter
používá Source={RelativeSource Self}
vazbu. Odkazuje Self
na aktuální objekt, což je CollectionView
. Všimněte si, že cesta vazby SelectedItem
je vlastnost. Když se příkaz vyvolá změnou vybrané položky, SelectNoteCommand
vyvolá se příkaz a vybraná položka se předá příkazu jako parametr.
Vyčištění kódu AllNotes
Teď, když se interakce se zobrazením změnila z obslužných rutin událostí na příkazy, otevřete soubor Views\AllNotesPage.xaml.cs a nahraďte veškerý kód třídou, která obsahuje pouze konstruktor:
V podokně Průzkumník řešení sady Visual Studio poklikejte na Zobrazení\AllNotesPage.xaml.cs.
Tip
Abyste mohli soubor zobrazit, budete možná muset rozbalit Zobrazení\AllNotesPage.xaml .
Nahraďte kód následujícím fragmentem kódu:
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); } }
Spustit aplikaci
Teď můžete aplikaci spustit a všechno funguje. Existují ale dva problémy s chováním aplikace:
- Pokud vyberete poznámku, která otevře editor, stisknete Uložit a pak se pokusíte vybrat stejnou poznámku, nebude fungovat.
- Při každé změně nebo přidání poznámky se seznam poznámek nepřespořádá tak, aby zobrazoval nejnovější poznámky v horní části.
Tyto dva problémy jsou opraveny v dalším kroku kurzu.
Oprava chování aplikace
Když teď může kód aplikace zkompilovat a spustit, pravděpodobně jste si všimli, že existují dvě chyby chování aplikace. Aplikace neumožňuje znovu vybrat vybranou poznámku a seznam poznámek se po vytvoření nebo změně poznámky nepřespořádá.
Získání poznámek na začátek seznamu
Nejprve opravte problém s změna pořadí u seznamu poznámek. V souboru ViewModels\NotesViewModel.cs obsahuje kolekce všechny poznámky, AllNotes
které se mají uživateli prezentovat. Nevýhodou použití ObservableCollection
je bohužel to, že se musí řadit ručně. Pokud chcete nové nebo aktualizované položky získat na začátek seznamu, proveďte následující kroky:
V podokně Průzkumník řešení sady Visual Studio poklikejte na ViewModels\NotesViewModel.cs.
ApplyQueryAttributes
V metodě se podívejte na logiku uloženého klíče řetězce dotazu.matchedNote
Pokud nenínull
, poznámka se aktualizuje.AllNotes.Move
Pomocí metody přesuňtematchedNote
index 0, což je začátek seznamu.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); }
Metoda
AllNotes.Move
přebírá dva parametry pro přesunutí pozice objektu v kolekci. První parametr je index objektu, který se má přesunout, a druhý parametr je index místa, kam se má objekt přesunout. MetodaAllNotes.IndexOf
načte index poznámky.Když je
null
,matchedNote
poznámka je nová a přidává se do seznamu. Místo přidání poznámky, která připojí poznámku na konec seznamu, vložte poznámku do indexu 0, což je začátek seznamu. Změňte metoduAllNotes.Add
naAllNotes.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)));
Metoda ApplyQueryAttributes
by měla vypadat jako následující fragment kódu:
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)));
}
}
Povolit výběr poznámky dvakrát
V zobrazeníCollectionView
AllNotes se zobrazí všechny poznámky, ale neumožňuje vybrat stejnou poznámku dvakrát. Položka zůstává vybraná dvěma způsoby: když uživatel změní existující poznámku a když uživatel vynutil přechod zpět. Případ, kdy uživatel uloží poznámku, je opravený změnou kódu v předchozí části, která používá AllNotes.Move
, takže se nemusíte starat o tento případ.
Problém, který teď musíte vyřešit, souvisí s navigací. Bez ohledu na to, jak je zobrazení Allnotes přecháděné, NavigatedTo
je událost vyvolána pro stránku. Tato událost je ideálním místem k vynucení výběru vybrané položky v sadě CollectionView
.
S použitým vzorem MVVM zde ale model viewmodel nemůže aktivovat něco přímo v zobrazení, například vymazání vybrané položky po uložení poznámky. Tak jak se to stane? Dobrá implementace vzoru MVVM minimalizuje kód v zobrazení. Existuje několik různých způsobů, jak tento problém vyřešit, aby podporoval model oddělení MVVM. Je ale také v pořádku vložit kód do kódu za zobrazení, zejména když je přímo svázaný s zobrazením. MVVM má mnoho skvělých návrhů a konceptů, které vám pomůžou rozčlenit aplikaci, zlepšit udržovatelnost a usnadnit přidávání nových funkcí. V některých případech ale můžete zjistit, že MVVM podporuje přeinženýrování.
Nepřeinfinujte řešení tohoto problému a jednoduše použijte NavigatedTo
událost k vymazání vybrané položky z objektu CollectionView
.
V podokně Průzkumník řešení sady Visual Studio poklikejte na Views\AllNotesPage.xaml.
Do XAML pro tuto
<ContentPage>
událost přidejteNavigatedTo
:<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>
Výchozí obslužnou rutinu události můžete přidat tak,
ContentPage_NavigatedTo
že kliknete pravým tlačítkem myši na název metody události a vyberete Přejít na definici. Tato akce otevře zobrazení\AllNotesPage.xaml.cs v editoru kódu.Nahraďte kód obslužné rutiny události následujícím fragmentem kódu:
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { notesCollection.SelectedItem = null; }
V XAML
CollectionView
byl uveden názevnotesCollection
. Tento kód používá tento název pro přístupCollectionView
k , a nastavenSelectedItem
nanull
. Vybraná položka se vymaže při každém přechodu na stránku.
Teď spusťte aplikaci. Zkuste přejít na poznámku, stiskněte tlačítko Zpět a vyberte stejnou poznámku podruhé. Chování aplikace je opravené.
Prozkoumejte kód pro tento kurz.. Pokud chcete stáhnout kopii dokončeného projektu pro porovnání kódu, stáhněte si tento projekt.
Gratulujeme!
Vaše aplikace teď používá vzory MVVM!
Další kroky
Následující odkazy obsahují další informace týkající se některých konceptů, které jste se naučili v tomto kurzu:
Máte s touto částí nějaké problémy? Pokud ano, dejte nám prosím vědět, abychom tuto část mohli vylepšit.