Поделиться через


Реализация MVVM с помощью набора средств MVVM

Теперь, когда у вас есть структура проекта, можно начать реализацию шаблона MVVM с помощью набора средств MVVM. Этот шаг включает создание ViewModels, использующих функции набора средств MVVM, такие как ObservableObject уведомление об изменении свойств и RelayCommand реализация команд.

Установите пакет NuGet MVVM Toolkit

Необходимо установить набор средств MVVM в проектах WinUINotes и WinUINotes.Bus .

Использование Visual Studio

  1. Щелкните правой кнопкой мыши проект WinUINotes.Bus в обозревателе решений.
  2. Выберите Manage NuGet Packages... (Управление пакетами NuGet...).
  3. Найдите CommunityToolkit.Mvvm и установите последнюю стабильную версию.
  4. Повторите эти действия для проекта WinUINotes .

Использование .NET CLI

Кроме того, для установки пакета можно использовать интерфейс командной строки .NET:

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

Решения по проектированию для уровня модели

При реализации MVVM важно решить, как структурировать классы моделей в отношении ViewModels. В этом руководстве классы моделей (Note и AllNotes) отвечают за представление данных, бизнес-логику и обновление хранилища данных. ViewModels обрабатывает наблюдаемые свойства, уведомления об изменениях и команды для взаимодействия с пользовательским интерфейсом.

В более простой реализации можно использовать обычные старые объекты CLR (POC) для классов моделей без методов бизнес-логики или доступа к данным. В этом случае ViewModels обрабатывает все операции данных через слой служб. Однако для этого руководства классы моделей включают методы загрузки, сохранения и удаления заметок, чтобы обеспечить более четкое разделение проблем и держать ViewModels сосредоточены на логике презентации.

Перемещение модели заметок

Переместите класс Note в проект WinUINotes.Bus. Он остается простым классом модели с некоторой логикой для управления данными и управления состоянием, но без каких-либо функций набора средств MVVM. ViewModels обрабатывает наблюдаемые свойства и уведомление об изменении, а не саму модель.

  1. В проекте WinUINotes.Bus создайте новую папку с именем Models.

  2. Note.cs Переместите файл из проекта WinUINotes в папку WinUINotes.Bus/Models.

  3. Обновите пространство имен, чтобы соответствовать новому расположению:

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

Класс Note — это простая модель данных. Для этого не требуется уведомление об изменении, так как ViewModels управляет наблюдаемыми свойствами и уведомляет пользовательский интерфейс об изменениях.

Перемещение модели AllNotes

Переместите класс AllNotes в проект WinUINotes.Bus.

  1. AllNotes.cs Переместите файл из проекта WinUINotes в папку WinUINotes.Bus/Models.

  2. Обновите пространство имен, чтобы соответствовать новому расположению:

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

Как и Note класс, AllNotes это простой класс модели. ViewModel обрабатывает наблюдаемое поведение и управляет коллекцией заметок.

Создайте AllNotesViewModel

  1. В проекте WinUINotes.Bus создайте новую папку с именем ViewModels.

  2. Добавьте новый файл AllNotesViewModel.cs класса с именем следующего содержимого:

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

Управляет AllNotesViewModel коллекцией заметок, отображаемых в пользовательском интерфейсе:

  • [ObservableProperty] notes: поле автоматически создает общедоступное Notes свойство с уведомлением об изменении. Notes При изменении коллекции пользовательский интерфейс автоматически обновляется.
  • allNotes модель. Это частное поле содержит экземпляр AllNotes модели, которая обрабатывает фактические операции с данными.
  • [RelayCommand]: этот атрибут создает LoadCommand свойство из LoadAsync() метода, позволяя пользовательскому интерфейсу активировать операцию загрузки через привязку данных.
  • LoadAsync() метод. Этот метод загружает заметки из модели, очищает текущую наблюдаемую коллекцию и заполняет ее загруженными заметками. Этот шаблон гарантирует, что коллекция, привязанная к пользовательскому интерфейсу, остается синхронизирована с базовыми данными.

Разделение между allNotes моделью (операциями с данными) и Notes наблюдаемой коллекцией (привязкой пользовательского интерфейса) является ключевым шаблоном MVVM, который разделяет обязанности и синхронизирует представление с данными ViewModel.

Дополнительные сведения см. в документации:

Создайте NoteViewModel

  1. В папке ViewModels добавьте новый файл класса с именем 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();
            }
        }
    }
    

NoteViewModel демонстрирует несколько ключевых функций MVVM Toolkit:

  • [ObservableProperty]: поля filename, text и date автоматически создают общедоступные свойства (Filename, Text, Date) с поддержкой уведомлений об изменении.
  • [NotifyCanExecuteChangedFor]: этот атрибут гарантирует, что при Filename изменении или Text изменении связанные команды повторно оценивают, могут ли они выполняться. Например, при вводе текста кнопка "Сохранить" автоматически включает или отключает на основе логики проверки.
  • [RelayCommand(CanExecute = nameof(CanSave))]: этот атрибут создает SaveCommand свойство, привязанное к методу CanSave()проверки. Эта команда активируется только в том случае, если у Text и Filename есть значения.
  • InitializeForExistingNote(): этот метод загружает существующие данные заметки в свойства ViewModel, которые затем обновляют пользовательский интерфейс с помощью привязки данных.
  • Логика сохранения: Метод Save() обновляет базовую Note модель с текущими значениями свойств и вызывает SaveAsync() модели. После сохранения он уведомляет DeleteCommand о том, что он должен быть повторно оценен (так как файл теперь существует и может быть удален).
  • Удаление логики: Delete() метод вызывает DeleteAsync() модель заметок и создает новую пустую заметку.

Далее в этом руководстве вы интегрируете службу файлов для обработки фактических операций с файлами и используете класс MVVM Toolkit WeakReferenceMessenger , чтобы уведомить другие части приложения, когда заметка удаляется, оставаясь слабо связанной.

Дополнительные сведения см. в документации:

Обновление представлений для использования ViewModels

Теперь необходимо обновить страницы XAML, чтобы привязаться к новым ViewModels.

Обновление представления AllNotesPage

  1. В AllNotesPage.xaml обновите привязку ItemsSource для ItemsView, чтобы использовать свойство Notes из ViewModel.

    <ItemsView ItemsSource="{x:Bind viewModel.Notes}"
    ...
    
  2. Обновите файл AllNotesPage.xaml.cs, чтобы он выглядел так:

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

В этом файле программного кода конструктор создает экземпляр AllNotesViewModel непосредственно. Метод OnNavigatedTo() вызывает LoadAsync() метод в ViewModel при переходе на страницу. Этот метод загружает заметки из хранилища и обновляет наблюдаемую коллекцию. Этот шаблон гарантирует, что данные всегда обновляются при переходе пользователя на страницу всех заметок.

Далее в этом руководстве вы рефакторите этот код для использования внедрения зависимостей, что позволяет внедрить ViewModel в конструктор страницы вместо создания напрямую. Этот подход повышает удобство тестирования и упрощает управление жизненными циклами ViewModel.

Обновить представление NotePage

  1. В NotePage.xaml обновите привязки TextBox для Text и Header, чтобы использовать свойства ViewModel. Обновите кнопки StackPanel, чтобы привязывать их к командам вместо использования событий 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>
    ...
    

    Вы также настраиваете UpdateSourceTrigger привязку TextBox.Text, чтобы изменения отправлялись в ViewModel по мере ввода данных пользователем. Этот параметр позволяет кнопке Save включить или отключить в режиме реального времени на основе входных данных.

  2. В NotePage.xaml.cs, обновите код, чтобы использовать 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);
                }
            }
        }
    }
    

    События Click для Save и Delete удаляются, так как кнопки теперь привязываются непосредственно к командам в ViewModel. Экземпляр NoteViewModel создается в методе OnNavigatedTo() . Если передается параметр Note, он инициализирует ViewModel с уже существующими данными заметки.

Дополнительные сведения см. в документации: