Compartilhar via


Implementar o MVVM com o Kit de Ferramentas MVVM

Agora que você tem a estrutura do projeto em vigor, 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 Kit de Ferramentas do MVVM, como ObservableObject para notificação de alteração de propriedade e RelayCommand para implementação de comando.

Instalar o pacote NuGet do Kit de Ferramentas MVVM

Você precisa instalar o Kit de Ferramentas MVVM 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. Pesquise CommunityToolkit.Mvvm e instale a versão estável mais recente.
  4. Repita estas 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 ao ViewModels. Neste tutorial, as classes de modelo (Note e AllNotes) são responsáveis pela representação de dados, pela lógica de negócios e pela atualização do armazenamento de dados. O ViewModels lida com propriedades observáveis, notificação de alteração e comandos para interação com a interface do usuário.

Em uma implementação mais simples, você pode usar POCOs (objetos CLR simples) antigos para as classes de modelo sem qualquer lógica de negócios ou métodos de acesso a dados. Nesse caso, o ViewModels manipula todas as operações de dados por meio 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 da apresentação.

Mover o modelo de nota

Mova a Note classe para o projeto WinUINotes.Bus . Ela continua sendo uma classe de modelo simples com alguma lógica para representação de dados e gerenciamento de estado, mas sem recursos do Kit de Ferramentas MVVM. Os ViewModels manipulam as propriedades observáveis e alteram a notificação, não o próprio modelo.

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

  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 das alterações.

Mover o modelo do 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 de modelo simples. O ViewModel manipula o comportamento observável e gerencia a coleção de anotações.

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 anotações 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 modelo: esse campo privado contém uma instância do AllNotes modelo, que manipula as operações de dados reais.
  • [RelayCommand]: esse atributo gera uma LoadCommand propriedade do LoadAsync() método, permitindo que a interface do usuário dispare a operação de carregamento por meio da associação de dados.
  • LoadAsync() método: esse método carrega anotações do modelo, limpa a coleção observável atual e a preenche com as anotações carregadas. Esse padrão garante que a coleção associada à interface do usuário permaneça sincronizada com os dados subjacentes.

A separação entre o allNotes modelo (operações de dados) e a Notes coleção observável (associação de interface do usuário) é um padrão MVVM chave que mantém as preocupações separadas e a exibição em sincronia 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();
            }
        }
    }
    

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 à notificação de alteração.
  • [NotifyCanExecuteChangedFor]: Esse atributo garante que, quando Filename ou Text se alteram, os comandos associados reavaliam se eles 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))]: esse atributo gera uma SaveCommand propriedade associada ao método CanSave()de validação. O comando só é habilitado quando tanto Text quanto Filename possuem valores.
  • InitializeForExistingNote(): esse método carrega os dados de uma anotação 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() no modelo. Depois de salvar, ele notifica DeleteCommand que ele deve ser reavaliado (já que um arquivo agora existe e pode ser excluído).
  • Excluir lógica: o método Delete() chama DeleteAsync() no modelo de anotação e cria uma nova nota vazia.

Posteriormente neste tutorial, você integrará o serviço de arquivo para lidar com as operações de arquivo reais e usar a classe do Kit de Ferramentas MVVM WeakReferenceMessenger para notificar outras partes do aplicativo quando uma anotação for excluída enquanto permanecer acoplada de forma flexível.

Saiba mais nos documentos:

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

Agora você precisa atualizar suas páginas XAML para associar ao novo ViewModels.

Atualizar o modo de exibição AllNotesPage

  1. Em AllNotesPage.xaml, atualize a ItemsSource vinculação do ItemsView para usar a propriedade Notes do ViewModel.

    <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 diretamente o AllNotesViewModel. O método OnNavigatedTo() chama o método LoadAsync() no ViewModel quando a página é acessada. Esse método carrega as anotações do armazenamento e atualiza a coleção observável. Esse método garante que os dados sejam sempre atualizados quando o usuário acessar a página Todas as Anotações.

Posteriormente neste tutorial, você refatora esse código para usar a injeção de dependência, o 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 de ciclos de vida ViewModel.

Atualizar a visualização NotePage

  1. Em NotePage.xaml, atualize as associações de TextBox para Text e Header, para usar as propriedades do ViewModel. Atualize os botões StackPanel para se vincular aos comandos em vez de usar os eventos 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>
    ...
    

    Você também define UpdateSourceTrigger na associação TextBox.Text para garantir que as alterações sejam enviadas ao ViewModel enquanto o usuário digita. Essa configuração permite que o Save botão habilite ou desabilite em tempo real com base na entrada.

  2. Em NotePage.xaml.cs, atualize o código para usar :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 eventos para Save e Delete são removidos, pois os botões agora vinculam-se diretamente aos comandos no ViewModel. O NoteViewModel é instanciado no método OnNavigatedTo(). Se um Note parâmetro for passado, ele inicializará o ViewModel com os dados de anotação existentes.

Saiba mais nos documentos: