Udostępnij przez


Implementowanie maszyny MVVM za pomocą zestawu narzędzi MVVM

Teraz, gdy masz już strukturę projektu, możesz zacząć implementować wzorzec MVVM przy użyciu zestawu narzędzi MVVM Toolkit. Ten krok obejmuje utworzenie modeli ViewModel, które korzystają z funkcji zestawu narzędzi MVVM Toolkit, takich jak ObservableObject powiadomienie o zmianie właściwości i RelayCommand implementacja poleceń.

Zainstaluj pakiet NuGet dla MVVM Toolkit

Należy zainstalować zestaw narzędzi MVVM w projektach WinUINotes i WinUINotes.Bus .

Korzystanie z programu Visual Studio

  1. Kliknij prawym przyciskiem myszy projekt WinUINotes.Bus w Eksploratorze rozwiązań.
  2. Wybierz Zarządzaj pakietami NuGet.
  3. Wyszukaj CommunityToolkit.Mvvm i zainstaluj najnowszą stabilną wersję.
  4. Powtórz te kroki dla projektu WinUINotes .

Korzystanie z .NET CLI

Alternatywnie możesz użyć interfejsu wiersza polecenia platformy .NET do zainstalowania pakietu:

dotnet add WinUINotes.Bus package CommunityToolkit.Mvvm
dotnet add WinUINotes package CommunityToolkit.Mvvm

Decyzje projektowe dotyczące warstwy modelu

Podczas implementacji wzorca MVVM ważne jest, aby zdecydować, jak zorganizować klasy modelu w odniesieniu do ViewModeli. W tym samouczku klasy modeli (Note i AllNotes) są odpowiedzialne za reprezentację danych, logikę biznesową i aktualizowanie magazynu danych. Modele Widoków obsługują właściwości obserwowalne, powiadomienia o zmianach i polecenia dla interakcji z interfejsem użytkownika.

W prostszej implementacji można użyć zwykłych starych obiektów CLR (POC) dla klas modelu bez żadnej logiki biznesowej lub metod dostępu do danych. W takim przypadku ViewModels zarządza wszystkimi operacjami na danych za pośrednictwem warstwy serwisowej. Jednak w tym samouczku klasy modeli obejmują metody ładowania, zapisywania i usuwania notatek, aby zapewnić jaśniejsze rozdzielenie zagadnień i zachować model ViewModels skoncentrowany na logice prezentacji.

Przenieś model Note

Przenieś klasę Note do projektu WinUINotes.Bus . Pozostaje to prosta klasa modelu z pewną logiką na potrzeby reprezentacji danych i zarządzania stanem, ale bez żadnych funkcji zestawu narzędzi MVVM Toolkit. Model widoków obsługuje obserwowalne właściwości i powiadomienia o zmianie, a nie sam model.

  1. W projekcie WinUINotes.Bus utwórz nowy folder o nazwie Models.

  2. Przenieś plik z projektu Note.cs do folderu WinUINotes.Bus/Models.

  3. Zaktualizuj przestrzeń nazw, aby odpowiadała nowej lokalizacji:

    namespace WinUINotes.Models
    {
        public class Note
        {
            // Existing code remains unchanged
            ...
        }
    }
    

Klasa Note jest prostym modelem danych. Nie wymaga powiadomienia o zmianie, ponieważ model ViewModels zarządza obserwowalnymi właściwościami i powiadamia interfejs użytkownika o zmianach.

Przenoszenie modelu AllNotes

Przenieś klasę AllNotes do projektu WinUINotes.Bus .

  1. Przenieś plik z projektu AllNotes.cs do folderu WinUINotes.Bus/Models.

  2. Zaktualizuj przestrzeń nazw, aby odpowiadała nowej lokalizacji:

    namespace WinUINotes.Models
    {
        public class AllNotes
        {
            // Existing code remains unchanged
            ...
        }
    }
    

Note Podobnie jak klasa, AllNotes jest prostą klasą modelu. ViewModel obsługuje zachowaniem obserwowalnym i zarządza zbiorem notatek.

Tworzenie modelu AllNotesViewModel

  1. W projekcie WinUINotes.Bus utwórz nowy folder o nazwie ViewModels.

  2. Dodaj nowy plik klasy o nazwie AllNotesViewModel.cs z następującą zawartością:

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Threading.Tasks;
    using WinUINotes.Models;
    
    namespace WinUINotes.ViewModels
    {
        public partial class AllNotesViewModel : ObservableObject
        {
            private readonly AllNotes allNotes;
    
            [ObservableProperty]
            private ObservableCollection<Note> notes;
    
            public AllNotesViewModel()
            {
                allNotes = new AllNotes();
                notes = new ObservableCollection<Note>();
            }
    
            [RelayCommand]
            public async Task LoadAsync()
            {
                await allNotes.LoadNotes();
                Notes.Clear();
                foreach (var note in allNotes.Notes)
                {
                    Notes.Add(note);
                }
            }
        }
    }
    

Moduł AllNotesViewModel zarządza kolekcją notatek wyświetlanych w interfejsie użytkownika.

  • [ObservableProperty]: Pole notes automatycznie generuje właściwość publiczną Notes z powiadomieniem o zmianie. Notes Gdy kolekcja ulegnie zmianie, interfejs użytkownika zostanie automatycznie zaktualizowany.
  • allNotes model: to pole prywatne zawiera wystąpienie AllNotes modelu, które obsługuje rzeczywiste operacje na danych.
  • [RelayCommand]: Ten atrybut generuje LoadCommand właściwość z LoadAsync() metody, umożliwiając interfejsowi użytkownika wyzwalanie operacji ładowania za pomocą powiązania danych.
  • LoadAsync() metoda: Ta metoda ładuje notatki z modelu, czyści bieżącą obserwowaną kolekcję i wypełnia je załadowanymi notatkami. Ten wzorzec gwarantuje, że kolekcja powiązana z interfejsem użytkownika pozostaje zsynchronizowana z danymi bazowymi.

Rozdzielenie allNotes modelu (operacji danych) i obserwowalnej Notes kolekcji (powiązanie interfejsu użytkownika) to kluczowy wzorzec MVVM, który zachowuje obawy oddzielone, a widok zsynchronizowany z danymi modelu ViewModel.

Dowiedz się więcej w dokumentacji:

Tworzenie modelu NoteViewModel

  1. W folderze ViewModels dodaj nowy plik klasy o nazwie NoteViewModel.cs:

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System;
    using System.Threading.Tasks;
    using WinUINotes.Models;
    
    namespace WinUINotes.ViewModels
    {
        public partial class NoteViewModel : ObservableObject
        {
            private Note note;
    
            [ObservableProperty]
            [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
            [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
            private string filename = string.Empty;
    
            [ObservableProperty]
            [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
            private string text = string.Empty;
    
            [ObservableProperty]
            private DateTime date = DateTime.Now;
    
            public NoteViewModel()
            {
                this.note = new Note();
                this.Filename = note.Filename;
            }
    
            public void InitializeForExistingNote(Note note)
            {
                this.note = note;
                this.Filename = note.Filename;
                this.Text = note.Text;
                this.Date = note.Date;
            }
    
            [RelayCommand(CanExecute = nameof(CanSave))]
            private async Task Save()
            {
                note.Filename = this.Filename;
                note.Text = this.Text;
                note.Date = this.Date;
                await note.SaveAsync();
    
                // Check if the DeleteCommand can now execute
                // (it can if the file now exists)
                DeleteCommand.NotifyCanExecuteChanged();
            }
    
            private bool CanSave()
            {
                return note is not null
                    && !string.IsNullOrWhiteSpace(this.Text)
                    && !string.IsNullOrWhiteSpace(this.Filename);
            }
    
            [RelayCommand(CanExecute = nameof(CanDelete))]
            private async Task Delete()
            {
                await note.DeleteAsync();
                note = new Note();
            }
    
            private bool CanDelete()
            {
                // Note: This is to illustrate how commands can be
                // enabled or disabled.
                // In a real application, you shouldn't perform
                // file operations in your CanExecute logic.
                return note is not null
                    && !string.IsNullOrWhiteSpace(this.Filename)
                    && this.note.NoteFileExists();
            }
        }
    }
    

Demonstruje NoteViewModel kilka kluczowych funkcji zestawu narzędzi MVVM Toolkit:

  • [ObservableProperty]: Pola filename, texti date automatycznie generują właściwości publiczne (Filename, Text, Date) z obsługą powiadomień o zmianie.
  • [NotifyCanExecuteChangedFor]: Ten atrybut gwarantuje, że gdy Filename lub Text zmieni się, skojarzone polecenia ponownie ocenią, czy mogą być wykonywane. Na przykład po wpisaniu tekstu przycisk Zapisz automatycznie włącza lub wyłącza na podstawie logiki walidacji.
  • [RelayCommand(CanExecute = nameof(CanSave))]: Ten atrybut generuje właściwość powiązaną SaveCommand z metodą CanSave()weryfikacji . Polecenie jest włączone tylko wtedy, gdy zarówno Text, jak i Filename mają wartości.
  • InitializeForExistingNote(): Ta metoda ładuje dane istniejącej notatki do właściwości ViewModel, które następnie aktualizują interfejs użytkownika za pomocą powiązania danych.
  • Logika zapisu: Save() metoda aktualizuje model bazowy Note przy użyciu bieżących wartości właściwości i wywołuje SaveAsync() na modelu. Po zapisaniu powiadamia DeleteCommand o tym, że powinien zostać ponownie obliczony (ponieważ plik istnieje teraz i można go usunąć).
  • Logika usuwania: Metoda Delete() wywołuje DeleteAsync() na modelu notatki i tworzy nową pustą notatkę.

Dalej w tym samouczku, zintegrujesz usługę plików w celu obsługi rzeczywistych operacji na plikach i użyjesz klasy WeakReferenceMessenger narzędzia MVVM Toolkit, aby powiadomić inne części aplikacji, gdy notatka zostanie usunięta, pozostając luźno powiązaną.

Dowiedz się więcej w dokumentacji:

Aktualizowanie widoków w celu korzystania z modelu ViewModels

Teraz musisz zaktualizować strony XAML, aby powiązać je z nowymi modelami ViewModels.

Aktualizowanie widoku AllNotesPage

  1. W AllNotesPage.xaml pliku zaktualizuj ItemsSource powiązanie ItemsView, aby użyć właściwości ViewModeluNotes.

    <ItemsView ItemsSource="{x:Bind viewModel.Notes}"
    ...
    
  2. Zaktualizuj plik AllNotesPage.xaml.cs, aby wyglądał następująco:

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using WinUINotes.ViewModels;
    
    namespace WinUINotes.Views
    {
        public sealed partial class AllNotesPage : Page
        {
            private AllNotesViewModel? viewModel;
    
            public AllNotesPage()
            {
                this.InitializeComponent();
                viewModel = new AllNotesViewModel();
            }
    
            private void NewNoteButton_Click(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(NotePage));
            }
    
            private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
            {
                Frame.Navigate(typeof(NotePage), args.InvokedItem);
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                if (viewModel is not null)
                {
                    await viewModel.LoadAsync();
                }
            }
        }
    }
    

W tym pliku code-behind konstruktor tworzy wystąpienie AllNotesViewModel bezpośrednio. Metoda OnNavigatedTo() wywołuje metodę LoadAsync() w modelu ViewModel po przejściu do strony. Ta metoda ładuje notatki z magazynu i aktualizuje obserwowaną kolekcję. Ten wzorzec gwarantuje, że dane są zawsze odświeżane, gdy użytkownik przejdzie do strony wszystkich notatek.

W dalszej części tego samouczka refaktoryzujesz ten kod w celu użycia wstrzykiwania zależności, co umożliwia wstrzyknięcie modelu ViewModel do konstruktora strony zamiast bezpośredniego tworzenia. Takie podejście zwiększa możliwość testowania i ułatwia zarządzanie cyklami życia modelu ViewModel.

Aktualizowanie widoku NotePage

  1. W NotePage.xaml, zaktualizuj powiązania TextBox dla Text i Header, aby używać właściwości ViewModel. StackPanel Zaktualizuj przyciski, aby powiązać z poleceniami zamiast używać zdarzeńClick:

    ...
    <TextBox x:Name="NoteEditor"
             Text="{x:Bind noteVm.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             AcceptsReturn="True"
             TextWrapping="Wrap"
             PlaceholderText="Enter your note"
             Header="{x:Bind noteVm.Date.ToString()}"
             ScrollViewer.VerticalScrollBarVisibility="Auto"
             MaxWidth="400"
             Grid.Column="1"/>
    
    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Right"
                Spacing="4"
                Grid.Row="1" Grid.Column="1">
        <Button Content="Save" Command="{x:Bind noteVm.SaveCommand}"/>
        <Button Content="Delete" Command="{x:Bind noteVm.DeleteCommand}"/>
    </StackPanel>
    ...
    

    Należy również ustawić powiązanie UpdateSourceTrigger na TextBox.Text, aby upewnić się, że zmiany są wysyłane do modelu ViewModel w trakcie pisania przez użytkownika. Ustawienie to pozwala, aby przycisk Save był włączany lub wyłączany w czasie rzeczywistym na podstawie danych wejściowych.

  2. W NotePage.xaml.cs pliku zaktualizuj kod, aby używał NoteViewModel:

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using WinUINotes.Models;
    using WinUINotes.ViewModels;
    
    namespace WinUINotes.Views
    {
        public sealed partial class NotePage : Page
        {
            private NoteViewModel? noteVm;
    
            public NotePage()
            {
                this.InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
                noteVm = new NoteViewModel();
    
                if (e.Parameter is Note note && noteVm is not null)
                {
                    noteVm.InitializeForExistingNote(note);
                }
            }
        }
    }
    

    Zdarzenia Click dla elementu Save i Delete są usuwane, ponieważ przyciski są teraz powiązane bezpośrednio z poleceniami w modelu ViewModel. NoteViewModel jest instancjonowany w metodzie OnNavigatedTo(). Jeśli zostanie przekazany parametr Note, inicjuje ViewModel z danymi istniejącej notatki.

Dowiedz się więcej w dokumentacji: