Partilhar via


Implementar MVVM com o MVVM Toolkit

Agora que você tem a estrutura do projeto no lugar, você pode começar a implementar o padrão MVVM usando o MVVM Toolkit. Esta etapa envolve a criação de ViewModels que aproveitam os recursos do MVVM Toolkit, como ObservableObject para notificação de alteração de propriedade e RelayCommand para implementação de comando.

Instalar o pacote NuGet do MVVM Toolkit

Você precisa instalar o MVVM Toolkit nos projetos WinUINotes e WinUINotes.Bus .

Usando o Visual Studio

  1. Clique com o botão direito do mouse no projeto WinUINotes.Bus no Gerenciador de Soluções.
  2. Selecione Gerenciar pacotes NuGet.
  3. Procure CommunityToolkit.Mvvm e instale a versão estável mais recente.
  4. Repita essas etapas para o projeto WinUINotes .

Usando a CLI do .NET

Como alternativa, você pode usar a CLI do .NET para instalar o pacote:

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

Decisões de design para a camada de modelo

Ao implementar o MVVM, é importante decidir como estruturar suas classes de modelo em relação aos ViewModels. Neste tutorial, as classes de modelo (Note e AllNotes) são responsáveis pela representação de dados, lógica de negócios e atualização do armazenamento de dados. Os ViewModels lidam com propriedades observáveis, notificação de alteração e comandos para interação da interface do usuário.

Em uma implementação mais simples, você pode usar objetos CLR antigos (POCOs) simples para as classes de modelo sem qualquer lógica de negócios ou métodos de acesso a dados. Nesse caso, os ViewModels lidam com todas as operações de dados através da camada de serviço. No entanto, para este tutorial, as classes de modelo incluem métodos para carregar, salvar e excluir anotações para fornecer uma separação mais clara de preocupações e manter os ViewModels focados na lógica de apresentação.

Mover o modelo de nota

Mova a Note classe para o projeto WinUINotes.Bus . Ele continua sendo uma classe de modelo simples com alguma lógica para representação de dados e gerenciamento de estado, mas sem nenhum recurso do MVVM Toolkit. Os ViewModels manipulam as propriedades observáveis e a notificação de alteração, não o modelo em si.

  1. No projeto WinUINotes.Bus , crie uma nova pasta chamada Models.

  2. Mova o Note.cs arquivo do projeto WinUINotes para a pasta WinUINotes.Bus/Models .

  3. Atualize o namespace para corresponder ao novo local:

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

A Note classe é um modelo de dados simples. Ele não precisa de notificação de alteração porque os ViewModels gerenciam propriedades observáveis e notificam a interface do usuário sobre alterações.

Mover o modelo AllNotes

Mova a AllNotes classe para o projeto WinUINotes.Bus .

  1. Mova o AllNotes.cs arquivo do projeto WinUINotes para a pasta WinUINotes.Bus/Models .

  2. Atualize o namespace para corresponder ao novo local:

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

Como a Note classe, AllNotes é uma classe modelo simples. O ViewModel lida com o comportamento observável e gerencia a coleção de notas.

Criar o AllNotesViewModel

  1. No projeto WinUINotes.Bus , crie uma nova pasta chamada ViewModels.

  2. Adicione um novo arquivo de classe nomeado AllNotesViewModel.cs com o seguinte conteúdo:

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

O AllNotesViewModel gerencia a coleção de notas exibidas na interface do usuário:

  • [ObservableProperty]: O notes campo gera automaticamente uma propriedade pública Notes com notificação de alteração. Quando a Notes coleção é alterada, a interface do usuário é atualizada automaticamente.
  • allNotes model: Este campo privado contém uma instância do AllNotes modelo, que lida com as operações de dados reais.
  • [RelayCommand]: Este atributo gera uma LoadCommand propriedade do LoadAsync() método, permitindo que a interface do usuário acione a operação de carregamento por meio da vinculação de dados.
  • LoadAsync() método: Este método carrega notas do modelo, limpa a coleção observável atual e a preenche com as notas carregadas. Esse padrão garante que a coleção vinculada à interface do usuário permaneça sincronizada com os dados subjacentes.

A separação entre o modelo (operações de allNotes dados) e a Notes coleção observável (vinculação da interface do usuário) é um padrão MVVM chave que mantém as preocupações separadas e o View sincronizado com os dados do ViewModel.

Saiba mais nos documentos:

Criar o NoteViewModel

  1. Na pasta ViewModels , adicione um novo arquivo de classe chamado 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();
            }
        }
    }
    

O NoteViewModel demonstra vários recursos principais do MVVM Toolkit:

  • [ObservableProperty]: Os campos filename, text e date geram automaticamente propriedades públicas (Filename, Text, Date) com suporte a notificação de alteração.
  • [NotifyCanExecuteChangedFor]: Este atributo garante que, quando Filename ou Text se alterarem, os comandos associados reavaliem se podem ser executados. Por exemplo, quando você digita texto, o botão Salvar habilita ou desabilita automaticamente com base na lógica de validação.
  • [RelayCommand(CanExecute = nameof(CanSave))]: Este atributo gera uma SaveCommand propriedade que está vinculada ao método CanSave()de validação. O comando só é habilitado quando Text e Filename têm valores.
  • InitializeForExistingNote(): Este método carrega os dados de uma nota existente nas propriedades ViewModel, que atualizam a interface do usuário por meio da associação de dados.
  • Lógica de salvamento: o Save() método atualiza o modelo subjacente Note com os valores de propriedade atuais e chama SaveAsync() o modelo. Depois de salvar, ele notifica que DeleteCommand deve reavaliar (já que um arquivo agora existe e pode ser excluído).
  • Lógica de exclusão: o Delete() método chama DeleteAsync() o modelo de nota e cria uma nova nota vazia.

Mais adiante neste tutorial, você integra o serviço de arquivo para lidar com as operações de arquivo reais e usa a classe do MVVM Toolkit WeakReferenceMessenger para notificar outras partes do aplicativo quando uma nota é excluída enquanto permanece fracamente acoplada.

Saiba mais nos documentos:

Atualizar os modos de exibição para usar os ViewModels

Agora você precisa atualizar suas páginas XAML para vincular aos novos ViewModels.

Atualizar o modo de exibição AllNotesPage

  1. No AllNotesPage.xaml, atualize a ItemsSource associação do ItemsView para usar a propriedade de ViewModel Notes :

    <ItemsView ItemsSource="{x:Bind viewModel.Notes}"
    ...
    
  2. Atualize o AllNotesPage.xaml.cs arquivo para ter esta aparência:

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

Neste arquivo code-behind, o construtor instancia o AllNotesViewModel diretamente. O método OnNavigatedTo() chama o método LoadAsync() no ViewModel quando se navega para a página. Este método carrega as notas do armazenamento e atualiza a coleção observável. Esse padrão garante que os dados sejam sempre atualizados quando o usuário navega para a página de todas as anotações.

Mais adiante neste tutorial, você refatora esse código para usar a injeção de dependência, que permite que o ViewModel seja injetado no construtor de página em vez de ser criado diretamente. Essa abordagem melhora a capacidade de teste e facilita o gerenciamento dos ciclos de vida do ViewModel.

Atualizar o modo de exibição NotePage

  1. No NotePage.xaml, atualize as TextBox associações para Text e Header de modo a utilizar as propriedades do ViewModel. Atualize os StackPanel botões para vincular aos comandos em vez de usar os Click eventos:

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

    Você também define a associação UpdateSourceTrigger em TextBox.Text para assegurar que as alterações são enviadas para o ViewModel à medida que o usuário digita. Essa configuração permite que o Save botão ative ou desative em tempo real com base na entrada.

  2. No NotePage.xaml.cs, atualize o código para usar o 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);
                }
            }
        }
    }
    

    Os Click eventos para Save e Delete são removidos, uma vez que os botões agora se ligam diretamente aos comandos no ViewModel. O NoteViewModel é instanciado no OnNavigatedTo() método. Se um Note parâmetro for passado, ele inicializa o ViewModel com os dados de nota existentes.

Saiba mais nos documentos: