Bagikan melalui


Meningkatkan aplikasi Anda dengan konsep MVVM

Tutorial ini dirancang untuk melanjutkan tutorial Membuat aplikasi .NET MAUI , yang membuat aplikasi pembuatan catatan. Dalam tutorial ini, Anda akan mempelajari cara:

  • Terapkan pola model-view-viewmodel (MVVM).
  • Gunakan gaya tambahan dari string kueri untuk mengirimkan data selama navigasi.

Kami menyarankan agar Anda terlebih dahulu mengikuti tutorial Membuat aplikasi .NET MAUI , karena kode yang dibuat dalam tutorial tersebut adalah dasar untuk tutorial ini. Jika Anda kehilangan kode, atau ingin memulai dari awal, unduh proyek ini.

Memahami MVVM

Pengalaman pengembang .NET MAUI biasanya melibatkan pembuatan antarmuka pengguna di XAML, lalu menambahkan code-behind yang beroperasi pada antarmuka pengguna. Masalah pemeliharaan yang kompleks dapat muncul saat aplikasi dimodifikasi dan tumbuh dalam ukuran dan cakupan. Masalah ini termasuk kopling ketat antara kontrol UI dan logika bisnis, yang meningkatkan biaya membuat modifikasi UI, dan kesulitan pengujian unit kode tersebut.

Pola model-viewmodel (MVVM) membantu memisahkan logika bisnis dan presentasi aplikasi dengan bersih dari antarmuka pengguna (UI). Mempertahankan pemisahan yang bersih antara logika aplikasi dan UI membantu mengatasi banyak masalah pengembangan dan membuat aplikasi lebih mudah diuji, dirawat, dan berkembang. Ini juga dapat secara signifikan meningkatkan peluang penggunaan kembali kode dan memungkinkan pengembang dan desainer UI untuk berkolaborasi dengan lebih mudah saat mengembangkan bagian masing-masing aplikasi.

Pola

Ada tiga komponen inti dalam pola MVVM: model, tampilan, dan model tampilan. Masing-masing melayani tujuan yang berbeda. Diagram berikut menunjukkan hubungan antara ketiga komponen.

Diagram yang menunjukkan bagian-bagian aplikasi yang dimodelkan MVVM

Selain memahami tanggung jawab setiap komponen, penting juga untuk memahami bagaimana mereka berinteraksi. Pada tingkat tinggi, tampilan "tahu tentang" model tampilan, dan model tampilan "tahu tentang" model, tetapi model tidak menyadari model tampilan, dan model tampilan tidak menyadari tampilan. Oleh karena itu, model tampilan mengisolasi tampilan dari model, dan memungkinkan model berevolusi secara independen dari tampilan.

Kunci untuk menggunakan MVVM secara efektif terletak pada pemahaman cara memperhitungkan kode aplikasi ke dalam kelas yang benar dan bagaimana kelas berinteraksi.

Tampilkan

Tampilan bertanggung jawab untuk menentukan struktur, tata letak, dan tampilan apa yang dilihat pengguna di layar. Idealnya, setiap tampilan didefinisikan dalam XAML, dengan kode terbatas di belakang yang tidak berisi logika bisnis. Namun, dalam beberapa kasus, code-behind mungkin berisi logika UI yang mengimplementasikan perilaku visual yang sulit diekspresikan di XAML, seperti animasi.

ViewModel

Model tampilan mengimplementasikan properti dan perintah tempat tampilan dapat mengikat data, dan memberi tahu tampilan perubahan status apa pun melalui peristiwa pemberitahuan perubahan. Properti dan perintah yang disediakan model tampilan menentukan fungsionalitas yang akan ditawarkan oleh UI, tetapi tampilan menentukan bagaimana fungsionalitas tersebut akan ditampilkan.

Model tampilan juga bertanggung jawab untuk mengoordinasikan interaksi tampilan dengan kelas model apa pun yang diperlukan. Biasanya ada hubungan satu-ke-banyak antara model tampilan dan kelas model.

Setiap model tampilan menyediakan data dari model dalam bentuk yang dapat dikonsumsi dengan mudah oleh tampilan. Untuk mencapai hal ini, model tampilan terkadang melakukan konversi data. Menempatkan konversi data ini dalam model tampilan adalah ide yang baik karena menyediakan properti yang dapat diikat tampilan. Misalnya, model tampilan mungkin menggabungkan nilai dua properti untuk mempermudah tampilan.

Penting

.NET MAUI memproses pembaruan binding ke utas UI. Saat menggunakan MVVM, ini memungkinkan Anda memperbarui properti viewmodel yang terikat data dari utas mana pun, dengan mesin pengikatan .NET MAUI menyampaikan pembaruan ke utas UI.

Modél

Kelas model adalah kelas non-visual yang merangkum data aplikasi. Oleh karena itu, model dapat dianggap sebagai mewakili model domain aplikasi, yang biasanya mencakup model data bersama dengan logika bisnis dan validasi.

Memperbarui model

Di bagian pertama tutorial ini, Anda akan menerapkan pola model-view-viewmodel (MVVM). Untuk memulai, buka solusi Notes.sln di Visual Studio.

Bersihkan model

Dalam tutorial sebelumnya, tipe model berfungsi baik sebagai model (data) maupun sebagai model tampilan (persiapan data), yang dipetakan langsung ke tampilan. Tabel berikut ini menjelaskan model:

File kode Deskripsi
Model/About.cs Model About. Bidang yang hanya dapat dibaca yang menjelaskan aplikasi itu sendiri, seperti judul dan versi aplikasi.
Model/Note.cs Model Note. Mewakili sebuah catatan.
Model/AllNotes.cs Model AllNotes. Memuat semua catatan pada perangkat ke dalam koleksi.

Memikirkan aplikasi itu sendiri, hanya ada satu bagian data yang digunakan oleh aplikasi, yaitu Note. Catatan dimuat dari perangkat, disimpan ke perangkat, dan diedit melalui UI aplikasi. Tidak ada kebutuhan untuk model About dan AllNotes. Hapus model ini dari proyek:

  1. Temukan panel Penjelajah Solusi di Visual Studio.
  2. Klik kanan pada file Model\About.cs dan pilih Hapus. Tekan OK untuk menghapus file.
  3. Klik kanan pada file Model\AllNotes.cs dan pilih Hapus. Tekan OK untuk menghapus file.

Satu-satunya file model yang tersisa adalah file Models\Note.cs .

Memperbarui model

Model Note berisi:

  • Pengidentifikasi unik, yang merupakan nama file catatan seperti yang disimpan di perangkat.
  • Teks catatan.
  • Tanggal untuk menunjukkan kapan catatan dibuat atau terakhir diperbarui.

Saat ini, memuat dan menyimpan model dilakukan melalui tampilan, dan dalam beberapa kasus, oleh jenis model lain yang baru saja Anda hapus. Kode yang Anda miliki untuk jenisnya Note harus sebagai berikut:

namespace Notes.Models;

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

Model Note ini akan diperluas untuk menangani pemuatan, penyimpanan, dan penghapusan catatan.

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali model\Note.cs.

  2. Di editor kode, tambahkan dua metode berikut ke Note kelas . Metode ini berbasis instans dan menangani penyimpanan atau penghapusan catatan saat ini ke atau dari perangkat, masing-masing:

    public void Save() =>
    File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
    
    public void Delete() =>
        File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
    
  3. Aplikasi ini perlu memuat catatan dengan dua cara, memuat catatan individual dari file dan memuat semua catatan di perangkat. Kode untuk mengelola pemuatan dapat menjadi anggota static, tidak memerlukan instans kelas untuk dijalankan.

    Tambahkan kode berikut ke kelas untuk memuat catatan berdasarkan nama file:

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

    Kode ini mengambil nama file sebagai parameter, membangun jalur ke tempat catatan disimpan di perangkat, dan mencoba memuat file jika ada.

  4. Cara kedua untuk memuat catatan adalah dengan menghitung semua catatan pada perangkat dan memuatnya ke dalam koleksi.

    Tambahkan kode berikut ke kelas :

    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);
    }
    

    Kode ini mengembalikan kumpulan jenis model yang dapat dijumlahkan Note dengan mengambil file pada perangkat yang cocok dengan pola file catatan: *.notes.txt. Setiap nama file diteruskan ke Load metode , memuat catatan individual. Akhirnya, kumpulan catatan diurutkan berdasarkan tanggal setiap catatan dan dikembalikan ke pemanggil.

  5. Terakhir, tambahkan konstruktor ke kelas yang mengatur nilai default untuk properti, termasuk nama file acak:

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

Kode Note kelas akan terlihat seperti berikut:

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);
    }
}

Sekarang setelah Note model selesai, model tampilan dapat dibuat.

Membuat viewmodel Tentang

Sebelum menambahkan model tampilan ke proyek, tambahkan referensi ke MVVM Community Toolkit. Pustaka ini tersedia di NuGet, dan menyediakan jenis dan sistem yang membantu menerapkan pola MVVM.

  1. Di panel Penjelajah Solusi Visual Studio, klik kanan proyek >Kelola Paket NuGet.

  2. Pilih tab Telusuri.

  3. Cari communitytoolkit mvvm dan pilih CommunityToolkit.Mvvm paket, yang seharusnya menjadi hasil pertama.

  4. Pastikan setidaknya versi 8 dipilih. Tutorial ini ditulis menggunakan versi 8.0.0.

  5. Selanjutnya, pilih Instal dan terima perintah apa pun yang ditampilkan.

    Mencari paket CommunityToolkit.Mvvm di NuGet.

Sekarang Anda siap untuk mulai memperbarui proyek dengan menambahkan model tampilan.

Memisahkan dengan model tampilan

Hubungan view-to-viewmodel sangat bergantung pada sistem pengikatan yang disediakan oleh .NET Multi-platform App UI (.NET MAUI). Aplikasi ini sudah menggunakan pengikatan data dalam tampilan untuk menampilkan daftar catatan dan menyajikan teks serta tanggal dari satu catatan. Logika aplikasi saat ini disediakan oleh kode tampilan di belakang dan langsung terkait dengan tampilan. Misalnya, saat pengguna mengedit catatan dan menekan tombol Simpan, peristiwa untuk tombol tersebut dipicu. Kemudian, code-behind untuk penanganan aktivitas menyimpan teks catatan ke file dan menavigasi ke layar sebelumnya.

Memiliki logika aplikasi di kode-belakang tampilan dapat menjadi masalah saat tampilan berubah. Misalnya, jika tombol diganti dengan kontrol input yang berbeda, atau nama kontrol diubah, penanganan aktivitas mungkin menjadi tidak valid. Terlepas dari bagaimana tampilan dirancang, tujuan tampilan adalah untuk memanggil semacam logika aplikasi dan menyajikan informasi kepada pengguna. Untuk aplikasi ini, tombol Save menyimpan catatan lalu menavigasi kembali ke layar sebelumnya.

Viewmodel memberi aplikasi tempat tertentu untuk menempatkan logika aplikasi terlepas dari bagaimana UI dirancang atau bagaimana data dimuat atau disimpan. Viewmodel adalah penghubung yang mewakili dan berinteraksi dengan model data untuk kepentingan tampilan.

Model tampilan disimpan dalam folder ViewModels .

  1. Temukan panel Penjelajah Solusi di Visual Studio.
  2. Klik kanan pada proyek Catatan dan pilih Tambahkan>Folder Baru. Beri nama folder ViewModels.
  3. Klik kanan pada folder >Tambahkan>Kelas dan beri nama AboutViewModel.cs.
  4. Ulangi langkah sebelumnya dan buat dua model tampilan lagi:
    • NoteViewModel.cs
    • NotesViewModel.cs

Struktur proyek Anda akan terlihat seperti gambar berikut:

Penjelajah solusi menampilkan folder MVVM.

Tentang tampilan viewmodel dan Tentang

Tampilan Tentang menampilkan beberapa data di layar dan secara opsional menavigasi ke situs web dengan informasi lebih lanjut. Karena tampilan ini tidak memiliki data apa pun untuk diubah, seperti dengan kontrol entri teks atau memilih item dari daftar, ini adalah kandidat yang baik untuk menunjukkan penambahan viewmodel. Untuk viewmodel Tentang, tidak ada model pendukung.

Buat Tentang viewmodel

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada ViewModels\AboutViewModel.cs.

  2. Tempelkan kode berikut:

    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);
    }
    

Cuplikan kode sebelumnya berisi beberapa properti yang mewakili informasi tentang aplikasi, seperti nama dan versi. Cuplikan ini persis sama dengan Tentang model yang Anda hapus sebelumnya. Namun, viewmodel ini berisi konsep baru, properti perintah ShowMoreInfoCommand.

Perintah adalah tindakan yang dapat diikat yang memanggil kode, dan merupakan tempat yang bagus untuk menempatkan logika aplikasi. Dalam contoh ini, ShowMoreInfoCommand menunjuk ke ShowMoreInfo metode , yang membuka browser web ke halaman tertentu. Anda akan mempelajari selengkapnya tentang sistem perintah di bagian berikutnya.

Tentang tampilan

Tampilan Tentang perlu sedikit diubah untuk menghubungkannya ke model tampilan yang dibuat di bagian sebelumnya. Dalam file Views\AboutPage.xaml , terapkan perubahan berikut:

  • xmlns:models Perbarui namespace XML ke xmlns:viewModels dan targetkan Notes.ViewModels namespace .NET.
  • Ubah properti ContentPage.BindingContext menjadi instans baru About viewmodel.
  • Hapus penanganan aktivitas tombol Clicked dan gunakan Command properti .

Perbarui halaman Tentang:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada Views\AboutPage.xaml.

  2. Tempelkan kode berikut:

    <?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>
    

    Cuplikan kode sebelumnya menyoroti baris yang telah berubah dalam versi tampilan ini.

Perhatikan bahwa tombol menggunakan Command properti . Banyak kontrol memiliki Command properti yang dipanggil saat pengguna berinteraksi dengan kontrol. Ketika digunakan dengan tombol, perintah dipanggil ketika pengguna menekan tombol , mirip dengan bagaimana Clicked penanganan aktivitas dipanggil, kecuali bahwa Anda dapat mengikat Command ke properti di viewmodel.

Dalam tampilan ini, ketika pengguna menekan tombol, Command dipanggil. Command terikat ke ShowMoreInfoCommand properti di viewmodel, dan ketika dipanggil, menjalankan kode dalam ShowMoreInfo metode , yang membuka browser web ke halaman tertentu.

Bersihkan Tentang kode di belakang

Tombol ShowMoreInfo tidak menggunakan penanganan aktivitas, sehingga LearnMore_Clicked kode harus dihapus dari file Views\AboutPage.xaml.cs . Hapus kode tersebut, kelas hanya boleh berisi konstruktor:

  1. Di panel Penjelajah Solusi Visual Studio, klik ganda pada View\AboutPage.xaml.cs.

    Petunjuk / Saran

    Anda mungkin perlu memperluas Views\AboutPage.xaml untuk menampilkan file.

  2. Ganti kode dengan cuplikan berikut:

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

Buat viewmodel Catatan

Tujuan memperbarui tampilan Catatan adalah untuk memindahkan fungsionalitas sebanyak mungkin dari kode XAML di belakang dan meletakkannya di viewmodel Catatan.

Catatan model tampilan

Sesuai dengan yang diperlukan oleh tampilan Catatan, viewmodel Catatan perlu menyediakan item berikut:

  • Teks catatan.
  • Tanggal/waktu catatan dibuat atau terakhir diperbarui.
  • Sebuah perintah untuk menyimpan catatan.
  • Perintah yang menghapus catatan.

Buat Catatan viewmodel:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada ViewModels\NoteViewModel.cs.

  2. Ganti kode dalam file ini dengan cuplikan berikut:

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

    Kode ini adalah viewmodel Note kosong di mana Anda akan menambahkan properti dan perintah untuk mendukung tampilan Note. Perhatikan bahwa CommunityToolkit.Mvvm.ComponentModel namespace sedang diimpor. Namespace ini menyediakan ObservableObject yang digunakan sebagai kelas dasar. Anda akan mempelajari lebih lanjut tentang ObservableObject di langkah berikutnya. Namespace CommunityToolkit.Mvvm.Input juga diimpor. Namespace ini menyediakan beberapa jenis perintah yang memanggil metode secara asinkron.

    Model Models.Note tersimpan sebagai bidang privat. Properti dan metode kelas ini akan menggunakan bidang ini.

  3. Tambahkan properti berikut ke kelas :

    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;
    

    Properti Date dan Identifier adalah properti sederhana yang hanya mengambil nilai yang sesuai dari model.

    Petunjuk / Saran

    Untuk properti, => sintaks membuat properti get-only di mana pernyataan di sebelah kanan => harus dievaluasi ke nilai yang akan dikembalikan.

    Properti Text pertama-tama memeriksa apakah nilai yang ditetapkan adalah nilai yang berbeda. Jika nilainya berbeda, nilai tersebut diteruskan ke properti model, dan metode dipanggil OnPropertyChanged .

    Metode OnPropertyChanged ini disediakan oleh ObservableObject kelas dasar. Metode ini menggunakan nama dari fungsi yang memanggil, dalam hal ini, nama properti Teks, dan memicu peristiwa ObservableObject.PropertyChanged. Kejadian ini menyediakan nama properti kepada pendengar acara mana pun. Sistem pengikatan yang disediakan oleh .NET MAUI mengenali peristiwa ini, dan memperbarui pengikatan terkait di UI. Untuk viewmodel Catatan, ketika Text properti berubah, kejadian diaktifkan, dan elemen UI yang terikat dengan Text properti diberi tahu tentang perubahan properti tersebut.

  4. Tambahkan properti perintah berikut ke kelas, yang merupakan perintah yang dapat diikat tampilan:

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }
    
  5. Tambahkan konstruktor berikut ke kelas :

    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);
    }
    

    Kedua konstruktor ini digunakan untuk membuat viewmodel dengan model pendukung baru, yang merupakan note kosong, atau untuk membuat viewmodel yang menggunakan instans model tertentu.

    Konstruktor juga menyiapkan perintah untuk viewmodel. Selanjutnya, tambahkan kode untuk perintah ini.

  6. Tambahkan metode Save dan 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}");
    }
    

    Metode ini dipanggil oleh perintah terkait. Mereka menjalankan tindakan terkait pada model dan membuat aplikasi berpindah ke halaman sebelumnya. Parameter kueri string ditambahkan ke .. jalur navigasi, yang menunjukkan tindakan yang diambil serta pengidentifikasi unik dari catatan tersebut.

  7. Selanjutnya, tambahkan ApplyQueryAttributes metode ke kelas , yang memenuhi persyaratan IQueryAttributable antarmuka:

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

    Saat halaman, atau konteks pengikatan halaman, mengimplementasikan antarmuka ini, parameter string kueri yang digunakan dalam navigasi diteruskan ke ApplyQueryAttributes metode . Viewmodel ini digunakan sebagai konteks pengikatan untuk tampilan Catatan. Saat tampilan Catatan dinavigasi, konteks pengikatan tampilan (viewmodel ini) menerima parameter string kueri yang digunakan selama navigasi.

    Kode ini memeriksa apakah load kunci disediakan dalam query kamus. Jika kunci ini ditemukan, nilainya harus menjadi pengidentifikasi (nama file) catatan yang akan dimuat. Catatan tersebut dimuat dan ditetapkan sebagai objek model yang mendasar dari instans viewmodel ini.

  8. Terakhir, tambahkan dua metode pembantu ini ke kelas :

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

    Metode Reload ini adalah metode pembantu yang me-refresh objek model pencadangan, memuat ulang dari penyimpanan perangkat

    Metode RefreshProperties adalah metode pembantu lain untuk memastikan bahwa setiap subscriber yang terikat ke objek ini diberi tahu bahwa properti Text dan Date telah berubah. Karena model yang mendasar (bidang _note) diubah saat catatan dimuat selama navigasi, properti Text dan Date sebenarnya tidak diatur ke nilai baru. Karena properti ini tidak diatur secara langsung, pengikatan apa pun yang dilampirkan ke properti tersebut tidak akan diberi tahu karena OnPropertyChanged tidak dipanggil untuk setiap properti. RefreshProperties memastikan pengikatan ke properti ini diperbarui.

Kode untuk kelas akan terlihat seperti cuplikan berikut:

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));
    }
}

Tampilan catatan

Sekarang setelah viewmodel dibuat, perbarui tampilan Catatan. Dalam file Views\NotePage.xaml , terapkan perubahan berikut:

  • xmlns:viewModels Tambahkan namespace XML yang menargetkan Notes.ViewModels namespace .NET.
  • Tambahkan BindingContext ke halaman.
  • Hapus penanganan aktivitas tombol Clicked hapus dan simpan dan ganti dengan perintah.

Perbarui tampilan Catatan:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali views\NotePage.xaml untuk membuka editor XAML.

  2. Tempelkan kode berikut:

    <?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>
    

Sebelumnya, tampilan ini tidak mendeklarasikan konteks pengikatan, karena disediakan oleh kode di belakang halaman itu sendiri. Mengatur konteks pengikatan langsung di XAML menyediakan dua hal:

  • Pada waktu proses, saat halaman dinavigasi, halaman akan menampilkan catatan kosong. Ini karena konstruktor tanpa parameter untuk konteks pengikatan, viewmodel, dipanggil. Jika Anda ingat dengan benar, konstruktor tanpa parameter untuk model tampilan Catatan membuat catatan kosong.

  • Intellisense di editor XAML menampilkan properti yang tersedia segera setelah Anda mulai mengetik sintaks {Binding. Sintaks juga divalidasi dan memberi tahu Anda tentang nilai yang tidak valid. Coba ubah sintaks pengikatan dari SaveCommand menjadi Save123Command. Jika Anda mengarahkan kursor mouse ke teks, Anda akan melihat bahwa tipsalat ditampilkan yang memberi tahu Anda bahwa Save123Command tidak ditemukan. Pemberitahuan ini tidak dianggap sebagai kesalahan karena pengikatan bersifat dinamis, ini benar-benar peringatan kecil yang mungkin membantu Anda melihat ketika Anda mengetik properti yang salah.

    Jika Anda mengubah SaveCommand ke nilai yang berbeda, pulihkan sekarang.

Membersihkan kode Catatan di belakang

Sekarang setelah interaksi dengan tampilan berubah dari penanganan aktivitas menjadi perintah, buka file Views\NotePage.xaml.cs dan ganti semua kode dengan kelas yang hanya berisi konstruktor:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada Views\NotePage.xaml.cs.

    Petunjuk / Saran

    Anda mungkin perlu memperluas Views\NotePage.xaml untuk menampilkan file.

  2. Ganti kode dengan cuplikan berikut:

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

Membuat ViewModel Catatan

Pasangan model tampilan akhir adalah model tampilan Catatan dan AllNotes. Namun, tampilan saat ini mengikat langsung ke model, yang dihapus pada awal tutorial ini. Tujuan dalam memperbarui tampilan AllNotes adalah untuk memindahkan fungsi sebanyak mungkin dari kode belakang XAML dan meletakkannya di viewmodel. Sekali lagi, manfaatnya adalah tampilan dapat mengubah desainnya dengan sedikit efek pada kode Anda.

Viewmodel Catatan

Berdasarkan tampilan yang akan ditunjukkan oleh tampilan AllNotes dan interaksi yang akan dilakukan oleh pengguna tersebut, model tampilan Catatan harus menyediakan item berikut:

  • Kumpulan catatan.
  • Perintah untuk mengakses catatan.
  • Perintah untuk membuat catatan baru.
  • Perbarui daftar catatan saat dibuat, dihapus, atau diubah.

Buat Catatan viewmodel:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada ViewModels\NotesViewModel.cs.

  2. Ganti kode dalam file ini dengan kode berikut:

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

    Kode ini adalah ruang kosong NotesViewModel di mana Anda akan menambahkan properti dan perintah untuk mendukung tampilan AllNotes.

  3. NotesViewModel Dalam kode kelas, tambahkan properti berikut:

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

    Properti AllNotes merupakan sebuah ObservableCollection yang menyimpan semua catatan yang dimuat dari perangkat. Dua perintah akan digunakan oleh tampilan untuk memicu tindakan membuat catatan atau memilih catatan yang sudah ada.

  4. Tambahkan konstruktor tanpa parameter ke kelas , yang menginisialisasi perintah dan memuat catatan dari model:

    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);
    }
    

    Perhatikan bahwa koleksi AllNotes menggunakan metode Models.Note.LoadAll untuk mengisi koleksi yang dapat diamati dengan catatan. Metode LoadAll mengembalikan catatan sebagai jenis Models.Note, tetapi koleksi yang dapat diamati adalah kumpulan jenis ViewModels.NoteViewModel. Kode ini menggunakan Select ekstensi Linq untuk membuat instance viewmodel dari model catatan yang diperoleh dari LoadAll.

  5. Buat metode yang ditargetkan oleh perintah:

    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}");
    }
    

    Perhatikan bahwa metode NewNoteAsync tidak mengambil parameter, sedangkan SelectNoteAsync mengambilnya. Perintah dapat secara opsional memiliki satu parameter yang disediakan saat perintah dipanggil. Untuk metode SelectNoteAsync, parameter tersebut mewakili catatan yang sedang dipilih.

  6. Terakhir, terapkan IQueryAttributable.ApplyQueryAttributes metode :

    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)));
        }
    }
    

    Viewmodel Catatan yang dibuat pada langkah tutorial sebelumnya menggunakan navigasi ketika catatan disimpan atau dihapus. Viewmodel dinavigasikan kembali ke tampilan AllNotes, yang mana view ini terkait dengan viewmodel tersebut. Kode ini mendeteksi apakah string kueri berisi deleted kunci atau saved . Nilai kunci adalah pengidentifikasi unik dari catatan.

    Jika catatan dihapus, maka catatan tersebut dicocokkan dalam kumpulan AllNotes menggunakan pengidentifikasi yang diberikan, dan dihapus.

    Ada dua kemungkinan alasan catatan disimpan. Catatan baru saja dibuat atau catatan yang sudah ada diubah. Jika catatan sudah ada dalam AllNotes koleksi, ini adalah catatan yang diperbarui. Dalam hal ini, instans catatan dalam koleksi hanya perlu diperbarui. Jika catatan hilang dari koleksi, ini adalah catatan baru dan harus ditambahkan ke koleksi.

Kode untuk kelas akan terlihat seperti cuplikan berikut:

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)));
        }
    }
}

Tampilan Semua Catatan

Sekarang setelah viewmodel dibuat, perbarui tampilan AllNotes untuk menunjuk ke properti viewmodel. Dalam file Views\AllNotesPage.xaml , terapkan perubahan berikut:

  • xmlns:viewModels Tambahkan namespace XML yang menargetkan Notes.ViewModels namespace .NET.
  • Tambahkan BindingContext ke halaman.
  • Hapus peristiwa tombol Clicked toolbar dan gunakan Command properti .
  • Ubah CollectionView untuk mengikat ItemSource-nya ke AllNotes.
  • Ubah CollectionView untuk menggunakan perintah dalam bereaksi ketika item terpilih berubah.

Perbarui tampilan AllNotes:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada Views\AllNotesPage.xaml.

  2. Tempelkan kode berikut:

    <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>
    

Toolbar tidak lagi menggunakan Clicked peristiwa dan sebaliknya menggunakan perintah.

CollectionView mendukung pengendalian dengan properti SelectionChangedCommand dan SelectionChangedCommandParameter. Dalam XAML yang diperbarui, SelectionChangedCommand properti terikat ke viewmodel SelectNoteCommand, yang berarti perintah dipanggil saat item yang dipilih berubah. Ketika perintah dipanggil, nilai properti SelectionChangedCommandParameter diteruskan ke perintah.

Lihat pengikatan yang digunakan untuk 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}">

Properti SelectionChangedCommandParameter menggunakan Source={RelativeSource Self} pengikatan. Self merujuk ke objek saat ini, yaitu CollectionView. Oleh karena itu, x:DataType menentukan CollectionView sebagai jenis untuk pengikatan yang dikompilasi. Perhatikan bahwa jalur pengikatan adalah SelectedItem properti . Ketika perintah dipanggil dengan mengubah item yang dipilih, SelectNoteCommand perintah dipanggil dan item yang dipilih diteruskan ke perintah sebagai parameter.

Agar ekspresi pengikatan yang didefinisikan dalam properti SelectionChangedCommandParameter dapat dikompilasi, perlu untuk menginstruksikan proyek untuk mengaktifkan penerapan pengikatan yang dikompilasi dalam ekspresi yang menentukan properti Source. Untuk melakukan ini, edit file proyek untuk solusi Anda dan tambahkan <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> dalam <PropertyGroup> elemen :

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

Membersihkan kode AllNotes di belakang

Sekarang setelah interaksi dengan tampilan berubah dari penanganan aktivitas menjadi perintah, buka file Views\AllNotesPage.xaml.cs dan ganti semua kode dengan kelas yang hanya berisi konstruktor:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada Views\AllNotesPage.xaml.cs.

    Petunjuk / Saran

    Anda mungkin perlu memperluas Views\AllNotesPage.xaml untuk menampilkan file.

  2. Ganti kode dengan cuplikan berikut:

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

Menjalankan aplikasi

Anda sekarang dapat menjalankan aplikasi, dan semuanya berfungsi. Namun, ada dua masalah dengan perilaku aplikasi:

  • Jika Anda memilih catatan, yang membuka editor, tekan Simpan, lalu coba pilih catatan yang sama, catatan tersebut tidak berfungsi.
  • Setiap kali catatan diubah atau ditambahkan, daftar catatan tidak diurutkan ulang untuk menampilkan catatan terbaru di bagian atas.

Kedua masalah ini diperbaiki pada langkah tutorial berikutnya.

Memperbaiki perilaku aplikasi

Sekarang setelah kode aplikasi dapat mengkompilasi dan menjalankan, Anda mungkin telah memperhatikan bahwa ada dua kelemahan dengan bagaimana aplikasi berperilaku. Aplikasi ini tidak memungkinkan Anda memilih kembali catatan yang sudah dipilih, dan daftar catatan tidak diurutkan ulang setelah catatan dibuat atau diubah.

Menempatkan catatan di bagian atas daftar

Pertama, perbaiki masalah penataan ulang dengan daftar catatan. Dalam file ViewModels\NotesViewModel.cs, koleksi AllNotes berisi semua catatan yang akan disajikan kepada pengguna. Sayangnya, kelemahan untuk menggunakan ObservableCollection adalah harus diurutkan secara manual. Untuk mendapatkan item baru atau yang diperbarui ke bagian atas daftar, lakukan langkah-langkah berikut:

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada ViewModels\NotesViewModel.cs.

  2. Dalam metode ApplyQueryAttributes, perhatikan logika untuk kunci kueri disimpan.

  3. Saat matchedNote bukan null, catatan sedang diperbarui. AllNotes.Move Gunakan metode untuk memindahkan matchedNote ke indeks 0, yang merupakan bagian atas daftar.

    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);
    }
    

    Metode ini AllNotes.Move mengambil dua parameter untuk memindahkan posisi objek dalam koleksi. Parameter pertama adalah indeks objek yang akan dipindahkan, dan parameter kedua adalah indeks tempat memindahkan objek. Metode AllNotes.IndexOf menemukan indeks catatan.

  4. Ketika matchedNote adalah null, catatan tersebut baru dan sedang ditambahkan ke daftar. Alih-alih menambahkannya, yang menambahkan catatan ke akhir daftar, sisipkan catatan di indeks 0, yang merupakan bagian atas daftar. Ubah metode menjadi AllNotes.AddAllNotes.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)));
    

Metode ApplyQueryAttributes ini akan terlihat seperti cuplikan kode berikut:

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)));
    }
}

Perbolehkan memilih catatan dua kali

Dalam tampilan AllNotes, CollectionView mencantumkan semua catatan, tetapi tidak memperbolehkan Anda memilih catatan yang sama dua kali. Ada dua cara item tetap dipilih: ketika pengguna mengubah catatan yang ada, dan ketika pengguna secara paksa menavigasi mundur. Kasus di mana pengguna menyimpan catatan diperbaiki dengan perubahan kode di bagian sebelumnya yang menggunakan AllNotes.Move, sehingga Anda tidak perlu khawatir tentang kasus tersebut.

Masalah yang harus Anda selesaikan sekarang terkait dengan navigasi. Tidak peduli bagaimana tampilan Allnotes diakses, NavigatedTo event dibangkitkan untuk halaman tersebut. Acara ini adalah tempat yang sempurna untuk secara paksa membatalkan pilihan item yang telah dipilih di CollectionView.

Namun, dengan pola MVVM yang diterapkan di sini, viewmodel tidak dapat memicu sesuatu langsung pada tampilan, seperti menghapus item yang dipilih setelah catatan disimpan. Jadi bagaimana Anda membuat hal itu terjadi? Implementasi pola MVVM yang baik meminimalkan kode di belakang dalam tampilan. Ada beberapa cara berbeda untuk menyelesaikan masalah ini untuk mendukung pola pemisahan MVVM. Namun, tidak apa-apa juga untuk menempatkan kode dalam code-behind dari tampilan, terutama ketika kode itu langsung terikat dengan tampilan. MVVM memiliki banyak desain dan konsep hebat yang membantu Anda mengkompartmentalisasi aplikasi, meningkatkan pemeliharaan, dan memudahkan Anda untuk menambahkan fitur baru. Namun, dalam beberapa kasus, Anda mungkin menemukan bahwa MVVM mendorong rekayasa berlebihan.

Jangan terlalu rumit dalam menyusun solusinya untuk masalah ini, dan cukup gunakan event NavigatedTo untuk menghapus item yang dipilih dari CollectionView.

  1. Di panel Penjelajah Solusi Visual Studio, klik dua kali pada Views\AllNotesPage.xaml.

  2. Dalam XAML untuk <ContentPage>, tambahkan event NavigatedTo.

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AllNotesPage"
                 Title="Your Notes"
                 NavigatedTo="ContentPage_NavigatedTo"
                 x:DataType="viewModels:NotesViewModel">
        <ContentPage.BindingContext>
            <viewModels:NotesViewModel />
    
  3. Anda dapat menambahkan penanganan aktivitas default dengan mengklik kanan pada nama metode peristiwa, ContentPage_NavigatedTo, dan memilih Buka Definisi. Tindakan ini membuka Views\AllNotesPage.xaml.cs di editor kode.

  4. Ganti kode penanganan aktivitas dengan cuplikan berikut:

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

    Dalam XAML, CollectionView diberikan nama notesCollection. Kode ini menggunakan nama tersebut untuk mengakses CollectionView, dan mengatur SelectedItem ke null. Item terpilih dibersihkan setiap kali halaman dinavigasi.

Sekarang, jalankan aplikasi Anda. Cobalah untuk menavigasi ke catatan, tekan tombol kembali, dan pilih catatan yang sama untuk kedua kalinya. Perilaku aplikasi diperbaiki!

Jelajahi kode. Jelajahi kode untuk tutorial ini.. Jika Anda ingin mengunduh salinan proyek yang telah selesai untuk membandingkan kode Anda dengan, unduh proyek ini.

Aplikasi Anda sekarang menggunakan pola MVVM!

Langkah selanjutnya

Tautan berikut memberikan informasi lebih lanjut yang terkait dengan beberapa konsep yang Anda pelajari dalam tutorial ini: