Compartilhar via


Atualizar seu aplicativo com conceitos de MVVM

Este tutorial foi projetado para continuar o tutorial Criar um aplicativo .NET MAUI , que criou um aplicativo de anotação. Neste tutorial, você aprenderá a:

  • Implemente o padrão modelo-visão-exibição de modelo (MVVM).
  • Use um estilo adicional de cadeia de caracteres de consulta para passar dados durante a navegação.

Sugerimos que você siga primeiro o tutorial Criar um aplicativo .NET MAUI , pois o código criado nesse tutorial é a base para este tutorial. Se você perdeu o código ou deseja iniciar novamente, baixe este projeto.

Compreender o MVVM

A experiência do desenvolvedor do .NET MAUI normalmente envolve a criação de uma interface do usuário em XAML e, em seguida, a adição de código subjacente que atua sobre a interface do usuário. Problemas complexos de manutenção podem surgir à medida que os aplicativos são modificados e crescem em tamanho e escopo. Esses problemas incluem o acoplamento apertado entre os controles de interface do usuário e a lógica de negócios, o que aumenta o custo de fazer modificações de interface do usuário e a dificuldade de testar esse código por unidade.

O padrão MVVM (model-view-viewmodel) ajuda a separar de forma clara a lógica de negócio e apresentação de um aplicativo de sua interface de usuário (UI). Manter uma separação limpa entre a lógica do aplicativo e a interface do usuário ajuda a resolver vários problemas de desenvolvimento e facilita o teste, a manutenção e a evolução de um aplicativo. Ele também pode melhorar significativamente as oportunidades de reutilização de código e permite que desenvolvedores e designers de interface do usuário colaborem com mais facilidade ao desenvolver suas respectivas partes de um aplicativo.

O padrão

Há três componentes principais no padrão MVVM: o modelo, a exibição e o modelo de exibição. Cada um serve a uma finalidade distinta. O diagrama a seguir mostra as relações entre os três componentes.

Um diagrama demonstrando as partes de um aplicativo modelado por MVVM

Além de entender as responsabilidades de cada componente, também é importante entender como eles interagem. Em um alto nível, a visão tem conhecimento do modelo de visão, e o modelo de visão tem conhecimento do modelo, mas o modelo não tem conhecimento do modelo de visão, e o modelo de visão não tem conhecimento da visão. Portanto, o modelo de exibição isola a exibição do modelo e permite que o modelo evolua independentemente da exibição.

A chave para usar o MVVM efetivamente reside no entendimento de como fatorar o código do aplicativo nas classes corretas e como as classes interagem.

Visualizar

A visão é responsável por definir a estrutura, o layout e a aparência do que o usuário vê na tela. Idealmente, cada exibição é definida em XAML, com um code-behind limitado que não contém lógica de negócios. No entanto, em alguns casos, o code-behind pode conter lógica de interface do usuário que implementa um comportamento visual difícil de expressar em XAML, como animações.

ViewModel

O modelo de exibição implementa propriedades e comandos aos quais a exibição pode associar dados e notifica a exibição de quaisquer alterações de estado por meio de eventos de notificação de alteração. As propriedades e os comandos que o modelo de exibição fornece definem a funcionalidade a ser oferecida pela interface do usuário, mas a exibição determina como essa funcionalidade deve ser exibida.

O modelo de visualização também é responsável por coordenar as interações com qualquer classe de modelo necessária. Normalmente, há uma relação um-para-muitos entre o modelo de visualização e as classes de modelo.

Cada modelo de visualização fornece dados de um modelo em uma forma que a visualização pode consumir facilmente. Para fazer isso, o modelo de exibição às vezes executa a conversão de dados. Colocar essa conversão de dados no modelo de exibição é uma boa ideia, pois fornece propriedades às quais a exibição pode se associar. Por exemplo, o modelo de visualização pode combinar os valores de duas propriedades para facilitar a exibição pela visualização.

Importante

O .NET MAUI encaminha atualizações de vinculação para o thread da interface do usuário. Ao usar o MVVM, isso permite que você atualize as propriedades do viewmodel associadas a dados de qualquer thread, enquanto o mecanismo de vinculação do .NET MAUI traz as atualizações para a thread da interface do usuário.

Modelo

Classes de modelo são classes não visuais que encapsulam os dados do aplicativo. Portanto, o modelo pode ser considerado como representando o modelo de domínio do aplicativo, que geralmente inclui um modelo de dados junto com a lógica de negócios e validação.

Atualizar o modelo

Nesta primeira parte do tutorial, você implementará o padrão MVVM (model-view-viewmodel). Para começar, abra a solução Notes.sln no Visual Studio.

Limpar o modelo

No tutorial anterior, os tipos de modelo estavam agindo como o modelo (dados) e como um modelo de exibição (preparação de dados), que foi mapeado diretamente para uma exibição. A tabela a seguir descreve o modelo:

Arquivo de código Descrição
Models/About.cs O About modelo. Contém campos somente leitura que descrevem o próprio aplicativo, como o título e a versão do aplicativo.
Models/Note.cs O Note modelo. Representa uma anotação.
Modelos/AllNotes.cs O AllNotes modelo. Carrega todas as anotações no dispositivo em uma única coleção.

Pensando no aplicativo em si, há apenas um dado que é usado pelo aplicativo, o Note. As anotações são carregadas do dispositivo, salvas no dispositivo e editadas por meio da interface do usuário do aplicativo. Realmente não há necessidade dos modelos About e AllNotes. Remova esses modelos do projeto:

  1. Localize o painel Gerenciador de Soluções do Visual Studio.
  2. Clique com o botão direito do mouse no arquivo Modelos\About.cs e selecione Excluir. Pressione OK para excluir o arquivo.
  3. Clique com o botão direito do mouse no arquivo Models\AllNotes.cs e selecione Excluir. Pressione OK para excluir o arquivo.

O único arquivo de modelo restante é o arquivo Models\Note.cs .

Atualizar o modelo

O Note modelo contém:

  • Um identificador exclusivo, que é o nome do arquivo da anotação, conforme armazenado no dispositivo.
  • O texto da nota.
  • Uma data para indicar quando a anotação foi criada ou atualizada pela última vez.

Atualmente, carregar e salvar o modelo foi feito por meio das exibições e, em alguns casos, pelos outros tipos de modelo que você acabou de remover. O código que você tem para o Note tipo deve ser o seguinte:

namespace Notes.Models;

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

O Note modelo será expandido para lidar com o carregamento, salvamento e exclusão de anotações.

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Modelos\Note.cs.

  2. No editor de código, adicione os dois métodos a seguir à Note classe. Esses métodos são baseados em instância e lidam com o salvamento ou exclusão da nota atual no dispositivo ou a partir dele, respectivamente.

    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. O aplicativo precisa carregar anotações de duas maneiras, carregando uma anotação individual de um arquivo e carregando todas as anotações no dispositivo. O código para lidar com o carregamento pode ser implementado como static membros, não requerendo a execução de uma instância de classe.

    Adicione o seguinte código à classe para carregar uma anotação pelo nome do arquivo:

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

    Esse código usa o nome do arquivo como um parâmetro, cria o caminho para onde as anotações são armazenadas no dispositivo e tenta carregar o arquivo se ele existir.

  4. A segunda maneira de carregar anotações é enumerar todas as anotações no dispositivo e carregá-las em uma coleção.

    Adicione o seguinte código à classe:

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

    Esse código retorna uma coleção enumerável de tipos de Note modelo recuperando os arquivos no dispositivo que correspondem ao padrão de arquivo de anotações: *.notes.txt. Cada nome de arquivo é passado para o método Load, para carregar uma nota individual. Por fim, a coleção de anotações é ordenada pela data de cada anotação e retornada ao chamador.

  5. Por fim, adicione um construtor à classe que define os valores padrão para as propriedades, incluindo um nome de arquivo aleatório:

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

O Note código de classe deve ser semelhante ao seguinte:

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

Agora que o Note modelo está concluído, os modelos de exibição podem ser criados.

Criar o ViewModel "Sobre"

Antes de adicionar modelos de exibição ao projeto, adicione uma referência ao Kit de Ferramentas da Comunidade MVVM. Essa biblioteca está disponível no NuGet e fornece tipos e sistemas que ajudam a implementar o padrão MVVM.

  1. No painel Gerenciador de Soluções do Visual Studio, clique com o botão direito do mouse no projeto Notas e selecione >.

  2. Selecione a guia Navegar.

  3. Pesquise por communitytoolkit mvvm e selecione o CommunityToolkit.Mvvm pacote, que deve ser o primeiro resultado.

  4. Verifique se pelo menos a versão 8 está selecionada. Este tutorial foi escrito usando a versão 8.0.0.

  5. Em seguida, selecione Instalar e aceitar todos os prompts exibidos.

    Pesquisando o pacote CommunityToolkit.Mvvm no NuGet.

Agora você está pronto para começar a atualizar o projeto adicionando modelos de exibição.

Desacoplar com modelos de exibição

A relação entre a view e o viewmodel depende bastante do sistema de vínculo fornecido pela .NET Multi-platform App UI (.NET MAUI). O aplicativo já está usando a associação nos modos de exibição para exibir uma lista de anotações e apresentar o texto e a data de uma única anotação. Atualmente, a lógica do aplicativo é fornecida pelo código subjacente da visualização e está diretamente vinculada à visualização. Por exemplo, quando um usuário está editando uma anotação e pressiona o botão Salvar , o Clicked evento para o botão é acionado. Em seguida, o código subjacente do manipulador de eventos salva o texto da nota em um arquivo e retorna à tela anterior.

Ter a lógica do aplicativo no code-behind de uma exibição pode se tornar um problema quando a exibição é alterada. Por exemplo, se o botão for substituído por um controle de entrada diferente ou o nome de um controle for alterado, os manipuladores de eventos poderão se tornar inválidos. Independentemente de como a exibição é projetada, a finalidade da exibição é invocar algum tipo de lógica de aplicativo e apresentar informações ao usuário. Para este aplicativo, o botão Save está salvando a nota e em seguida navegando de volta para a tela anterior.

O viewmodel fornece ao aplicativo um lugar específico para colocar a lógica do aplicativo independentemente de como a interface do usuário foi projetada ou como os dados estão sendo carregados ou salvos. O viewmodel é a cola que representa e interage com o modelo de dados em nome da exibição.

Os modelos de exibição são armazenados em uma pasta ViewModels .

  1. Localize o painel Gerenciador de Soluções do Visual Studio.
  2. Clique com o botão direito do mouse no projeto Anotações e selecione Adicionar>Nova Pasta. Nomeie a pasta ViewModels.
  3. Clique com o botão direito do mouse na pasta >>viewModels e nomeie-a AboutViewModel.cs.
  4. Repita a etapa anterior e crie mais dois modelos de exibição:
    • NoteViewModel.cs
    • NotesViewModel.cs

Sua estrutura de projeto deve ser semelhante à seguinte imagem:

Gerenciador de soluções mostrando pastas MVVM.

Sobre o Viewmodel e Sobre o modo de exibição

A exibição Sobre exibe alguns dados na tela e, opcionalmente, navega até um site com mais informações. Como essa visão não tem dados a serem modificados, da mesma forma que um controle de entrada de texto ou ao selecionar itens de uma lista, é um bom candidato para demonstrar a adição de um viewmodel. Para o viewmodel Sobre, não há um modelo subjacente.

Crie o viewmodel Sobre:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em ViewModels\AboutViewModel.cs.

  2. Cole o código a seguir:

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

O snippet de código anterior contém algumas propriedades que representam informações sobre o aplicativo, como o nome e a versão. Esse trecho é exatamente o mesmo que o modelo Sobre que você excluiu anteriormente. No entanto, esse viewmodel contém um novo conceito, a ShowMoreInfoCommand propriedade de comando.

Os comandos são ações associáveis que invocam código e são um ótimo lugar para colocar a lógica do aplicativo. Neste exemplo, o ShowMoreInfoCommand aponta para o ShowMoreInfo método, que abre o navegador da Web em uma página específica. Você aprenderá mais sobre o sistema de comandos na próxima seção.

Sobre a exibição

O visão Sobre precisa ser alterado ligeiramente para ligá-lo ao viewmodel criado na seção anterior. No arquivo Views\AboutPage.xaml , aplique as seguintes alterações:

  • Atualize o namespace XML xmlns:models para xmlns:viewModels e direcione o namespace .NET Notes.ViewModels.
  • Altere a ContentPage.BindingContext propriedade para uma nova instância do About viewmodel.
  • Remova o manipulador de eventos do botão Clicked e use a propriedade Command.

Atualize a visão 'Sobre':

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Views\AboutPage.xaml.

  2. Cole o código a seguir:

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

    O trecho de código anterior destaca as linhas que foram alteradas na versão desta visualização.

Observe que o botão está usando a Command propriedade. Muitos controles têm uma Command propriedade que é invocada quando o usuário interage com o controle. Quando usado com um botão, o comando é invocado quando um usuário pressiona o botão, semelhante à maneira como o Clicked manipulador de eventos é invocado, exceto que você pode associar Command a uma propriedade no viewmodel.

Nesse modo de exibição, quando o usuário pressiona o botão, o Command é invocado. O Command está vinculado à propriedade ShowMoreInfoCommand no viewmodel e, quando invocado, executa o código no método ShowMoreInfo, que abre o navegador web para uma página específica.

Limpar o code-behind sobre

O ShowMoreInfo botão não está usando o manipulador de eventos, portanto, o LearnMore_Clicked código deve ser removido do arquivo Views\AboutPage.xaml.cs . Excluir esse código, a classe deve conter apenas o construtor:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Exibições\AboutPage.xaml.cs.

    Dica

    Talvez seja necessário expandir Views\AboutPage.xaml para mostrar o arquivo.

  2. Substitua o código pelo seguinte snippet:

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

Criar o viewmodel de nota

O objetivo de atualizar o modo de exibição Observação é mover o máximo de funcionalidade possível para fora do code-behind XAML e colocá-lo no modo de exibição de Observação.

Observação de viewmodel

Com base no que a exibição de Nota requer, o viewmodel de Nota precisa fornecer os seguintes itens:

  • O texto da nota.
  • A data/hora em que a anotação foi criada ou atualizada pela última vez.
  • Um comando que salva a anotação.
  • Um comando que exclui a anotação.

Crie o viewmodel de Nota:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em ViewModels\NoteViewModel.cs.

  2. Substitua o código neste arquivo pelo seguinte snippet:

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

    Esse código é o viewmodel em branco Note em que você adicionará propriedades e comandos para dar suporte à visualização Note. Observe que o CommunityToolkit.Mvvm.ComponentModel namespace está sendo importado. Esse namespace fornece o ObservableObject usado como a classe base. Você aprenderá mais sobre ObservableObject na próxima etapa. O CommunityToolkit.Mvvm.Input namespace também é importado. Esse namespace fornece alguns tipos de comando que invocam métodos de forma assíncrona.

    O Models.Note modelo está sendo armazenado como um campo privado. As propriedades e os métodos dessa classe usarão esse campo.

  3. Adicione as seguintes propriedades à classe:

    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;
    

    As Date propriedades e as Identifier propriedades são simples que apenas recuperam os valores correspondentes do modelo.

    Dica

    Para propriedades, a sintaxe => cria uma propriedade somente de leitura em que a instrução à direita de => deve ser avaliada para ser retornada.

    A Text propriedade primeiro verifica se o valor que está sendo definido é um valor diferente. Se o valor for diferente, esse valor será passado para a propriedade do modelo e o OnPropertyChanged método será chamado.

    O OnPropertyChanged método é fornecido pela ObservableObject classe base. Esse método usa o nome do código de chamada, nesse caso, o nome da propriedade Text, e aciona o evento ObservableObject.PropertyChanged. Esse evento fornece o nome da propriedade para todos os assinantes do evento. O sistema de associação fornecido pelo .NET MAUI reconhece esse evento e atualiza todas as associações relacionadas na interface do usuário. Para o ViewModel, quando a propriedade Text é alterada, o evento é disparado e qualquer elemento de interface do usuário ligado à propriedade Text é notificado de que a propriedade foi alterada.

  4. Adicione as seguintes propriedades de comando à classe, que são os comandos aos quais a exibição pode se associar:

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }
    
  5. Adicione os seguintes construtores à classe:

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

    Esses dois construtores são usados para criar o viewmodel com um novo modelo de apoio, que é uma nota vazia, ou para criar um viewmodel que usa a instância de modelo especificada.

    Os construtores também configuram os comandos para o modelo de visualização. Em seguida, adicione o código para esses comandos.

  6. Adicione os métodos Save e 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}");
    }
    

    Esses métodos são invocados por comandos associados. Eles executam as ações relacionadas no modelo e fazem com que o aplicativo navegue até a página anterior. Um parâmetro de cadeia de caracteres de consulta é adicionado ao .. caminho de navegação, indicando qual ação foi executada e o identificador exclusivo da nota.

  7. Em seguida, adicione o ApplyQueryAttributes método à classe, que atende aos requisitos da IQueryAttributable interface:

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

    Quando uma página ou o contexto de associação de uma página implementa essa interface, os parâmetros de cadeia de caracteres de consulta usados na navegação são passados para o ApplyQueryAttributes método. Este viewmodel é usado como contexto de associação para a visualização Nota. Quando se navega para a visualização Nota, o contexto de associação da visualização (este modelo de visualização) recebe os parâmetros da cadeia de consulta usados durante a navegação.

    Esse código verifica se a load chave foi fornecida no query dicionário. Se essa chave for encontrada, o valor deverá ser o identificador (o nome do arquivo) da anotação a ser carregada. Essa nota é carregada e definida como o objeto de modelo subjacente da instância de viewmodel.

  8. Por fim, adicione estes dois métodos auxiliares à classe:

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

    O Reload método é um método auxiliar que atualiza o objeto de modelo de backup, recarregando-o do armazenamento do dispositivo

    O método RefreshProperties é outro auxiliar para garantir que todos os assinantes ligados a esse objeto sejam notificados de que as propriedades Text e Date foram alteradas. Como o modelo subjacente (o _note campo) é alterado quando a nota é carregada durante a navegação, as propriedades Text e Date não são realmente definidas como novos valores. Como essas propriedades não são definidas diretamente, nenhuma associação anexada a essas propriedades não seria notificada porque OnPropertyChanged não é chamada para cada propriedade. RefreshProperties garante que as associações a essas propriedades sejam atualizadas.

O código da classe deve ser semelhante ao seguinte snippet:

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

Visualização de notas

Agora que o viewmodel foi criado, atualize o Note view. No arquivo Views\NotePage.xaml , aplique as seguintes alterações:

  • Adicione o xmlns:viewModels namespace XML direcionado ao Notes.ViewModels namespace do .NET.
  • Adicione um BindingContext à página.
  • Remova os manipuladores de eventos de botão Clicked excluir e salvar e substitua-os por comandos.

Atualize a visualização de nota:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Views\NotePage.xaml para abrir o editor XAML.

  2. Cole o código a seguir:

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

Anteriormente, esse modo de exibição não declarava um contexto de associação, pois era fornecido pelo code-behind da própria página. Definir o contexto de associação diretamente no XAML fornece duas coisas:

  • Durante a execução, quando a página é acessada, ela exibe uma nota em branco. Isso ocorre porque o construtor sem parâmetros para o contexto de associação, o viewmodel, é invocado. Se você se lembrar corretamente, o construtor sem parâmetros do viewmodel de Observação produz uma nota em branco.

  • O intellisense no editor XAML mostra as propriedades disponíveis assim que você começa a digitar {Binding a sintaxe. A sintaxe também é validada e alerta você sobre um valor inválido. Tente alterar a sintaxe de associação de SaveCommand para Save123Command. Se você passar o cursor do mouse sobre o texto, observará que uma dica de ferramenta é exibida informando que Save123Command não foi encontrado. Essa notificação não é considerada um erro porque as associações são dinâmicas, é realmente um aviso pequeno que pode ajudá-lo a notar quando você digitou a propriedade errada.

    Se você alterou o SaveCommand para um valor diferente, restaure-o agora.

Limpar o code-behind da Nota

Agora que a interação com a exibição foi alterada de manipuladores de eventos para comandos, abra o arquivo Views\NotePage.xaml.cs e substitua todo o código por uma classe que contém apenas o construtor:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Exibições\NotePage.xaml.cs.

    Dica

    Talvez seja necessário expandir Views\NotePage.xaml para mostrar o arquivo.

  2. Substitua o código pelo seguinte snippet:

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

Criar o modelo de visão de Anotações

O par viewmodel-view final é o Notes viewmodel e AllNotes view. No momento, entretanto, a exibição está vinculada diretamente ao modelo, que foi deletado no início deste tutorial. A meta na atualização da AllNotes view é mover o máximo de funcionalidade possível para fora do code-behind XAML e colocá-la no modelo de visualização. Novamente, o benefício é que a visualização pode alterar seu design com pouco efeito sobre o seu código.

Modelo de Visualização de Notas

Com base no que a exibição AllNotes vai mostrar e nas interações que o usuário realizará, o viewmodel de Anotações deve fornecer os seguintes itens:

  • Uma coleção de anotações.
  • Um comando para lidar com a navegação até uma anotação.
  • Um comando para criar uma nova anotação.
  • Atualize a lista de anotações quando uma é criada, excluída ou alterada.

Crie o viewmodel de Anotações:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em ViewModels\NotesViewModel.cs.

  2. Substitua o código neste arquivo pelo seguinte código:

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

    Esse código é o espaço em branco NotesViewModel em que você adicionará propriedades e comandos para dar suporte à exibição AllNotes .

  3. No código de NotesViewModel classe, adicione as seguintes propriedades:

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

    A propriedade AllNotes é uma ObservableCollection que armazena todas as anotações carregadas do dispositivo. Os dois comandos serão usados pela visão para disparar as ações de criação de uma nota ou seleção de uma nota existente.

  4. Adicione um construtor sem parâmetros à classe, que inicializa os comandos e carrega as anotações do modelo:

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

    Observe que a AllNotes coleção usa o Models.Note.LoadAll método para preencher a coleção observável com anotações. O LoadAll método retorna as anotações como o Models.Note tipo, mas a coleção observável é uma coleção de ViewModels.NoteViewModel tipos. O código usa a extensão Linq para criar instâncias de ViewModel a partir dos modelos de anotação retornados de Select.

  5. Crie os métodos aos quais os comandos são direcionados:

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

    Observe que o NewNoteAsync método não usa um parâmetro enquanto o SelectNoteAsync faz. Opcionalmente, os comandos podem ter um único parâmetro fornecido quando o comando é invocado. Para o método SelectNoteAsync, o parâmetro representa a nota que está sendo selecionada.

  6. Por fim, implemente o IQueryAttributable.ApplyQueryAttributes método:

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

    O viewmodel de Nota criado na etapa anterior do tutorial, utilizou a navegação quando a nota foi salva ou excluída. O viewmodel navegou de volta para o modo de exibição AllNotes, ao qual esse viewmodel está associado. Esse código detecta se a string de consulta contém a chave deleted ou a chave saved. O valor da chave é o identificador exclusivo da nota.

    Se a anotação tiver sido excluída, essa anotação será correspondida na AllNotes coleção pelo identificador fornecido e removida.

    Há dois motivos possíveis para salvar uma anotação. A anotação foi recém-criada ou uma nota existente foi alterada. Se a anotação já estiver na AllNotes coleção, será uma anotação que foi atualizada. Nesse caso, a instância de anotação na coleção só precisa ser atualizada. Se a anotação estiver ausente da coleção, ela será uma nova anotação e deverá ser adicionada à coleção.

O código da classe deve ser semelhante ao seguinte snippet:

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

Visualização do AllNotes

Agora que o viewmodel foi criado, atualize a exibição AllNotes para apontar para as propriedades do viewmodel. No arquivo Views\AllNotesPage.xaml , aplique as seguintes alterações:

  • Adicione o xmlns:viewModels namespace XML direcionado ao Notes.ViewModels namespace do .NET.
  • Adicione um BindingContext à página.
  • Remova o evento do botão da barra de ferramentas Clicked e use a propriedade Command.
  • Altere o CollectionView para vincular seu ItemSource a AllNotes.
  • Altere o CollectionView para usar comandos que reagem às mudanças do item selecionado.

Atualize a visualização AllNotes:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Views\AllNotesPage.xaml.

  2. Cole o código a seguir:

    <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="{FontImage 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>
    

A barra de ferramentas não usa mais o Clicked evento e, em vez disso, usa um comando.

O CollectionView oferece suporte a comandos com as propriedades SelectionChangedCommand e SelectionChangedCommandParameter. No XAML atualizado, a propriedade SelectionChangedCommand está vinculada ao SelectNoteCommand do viewmodel, o que significa que o comando é invocado quando o item selecionado é alterado. Quando o comando é invocado, o valor da SelectionChangedCommandParameter propriedade é passado para o comando.

Olhe para a associação usada para o 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}">

A SelectionChangedCommandParameter propriedade usa associação Source={RelativeSource Self} . Faz Self referência ao objeto atual, que é o CollectionView. Portanto, x:DataType especifica CollectionView como o tipo para a associação compilada. Observe que o caminho de associação é a SelectedItem propriedade. Quando o comando é invocado alterando o item selecionado, o SelectNoteCommand comando é invocado e o item selecionado é passado para o comando como um parâmetro.

Para que a expressão de vinculação definida na propriedade SelectionChangedCommandParameter seja compilada, é necessário instruir o projeto a habilitar vinculações compiladas em expressões que especificam a propriedade Source. Para fazer isso, edite o arquivo de projeto para sua solução e adicione <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> dentro do <PropertyGroup> elemento:

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

Limpar o backend do código do AllNotes

Agora que a interação com o modo de exibição foi alterada de manipuladores de eventos para comandos, abra o arquivo Views\AllNotesPage.xaml.cs e substitua todo o código por uma classe que contém apenas o construtor:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Exibições\AllNotesPage.xaml.cs.

    Dica

    Talvez seja necessário expandir Views\AllNotesPage.xaml para mostrar o arquivo.

  2. Substitua o código pelo seguinte snippet:

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

Executar o aplicativo

Agora você pode executar o aplicativo e tudo está funcionando. No entanto, há dois problemas com o comportamento do aplicativo:

  • Se você selecionar uma nota, que abre o editor, pressione Salvar e, em seguida, tente selecionar a mesma nota, ela não funcionará.
  • Sempre que uma anotação é alterada ou adicionada, a lista de anotações não é reordenada para mostrar as notas mais recentes na parte superior.

Esses dois problemas são corrigidos na próxima etapa do tutorial.

Corrigir o comportamento do aplicativo

Agora que o código do aplicativo pode compilar e executar, você provavelmente terá notado que há duas falhas na forma como o aplicativo se comporta. O aplicativo não permite que você reelege uma anotação já selecionada e a lista de anotações não é reordenada depois que uma anotação é criada ou alterada.

Colocar anotações no topo da lista

Primeiro, corrija o problema de reordenação com a lista de anotações. No arquivo ViewModels\NotesViewModel.cs , a AllNotes coleção contém todas as anotações a serem apresentadas ao usuário. Infelizmente, a desvantagem de usar um ObservableCollection é que ele deve ser classificado manualmente. Para obter os itens novos ou atualizados na parte superior da lista, execute as seguintes etapas:

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em ViewModels\NotesViewModel.cs.

  2. No método ApplyQueryAttributes, examine a lógica para a chave de consulta de cadeia de caracteres salva.

  3. Quando matchedNote não é null, a anotação está sendo atualizada. Use o método AllNotes.Move para mover o matchedNote para o índice 0, que é a parte superior da lista.

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

    O AllNotes.Move método usa dois parâmetros para mover a posição de um objeto na coleção. O primeiro parâmetro é o índice do objeto que será movido e o segundo parâmetro é o índice de onde mover o objeto. O AllNotes.IndexOf método recupera o índice da anotação.

  4. Quando o matchedNote estiver null, a nota será nova e será adicionada à lista. Em vez de adicioná-la, que acrescenta a nota ao final da lista, insira a nota no índice 0, que é a parte superior da lista. Altere o AllNotes.Add método para AllNotes.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)));
    

O método ApplyQueryAttributes deve se parecer com o seguinte trecho de código:

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

Permitir selecionar uma nota duas vezes

No modo de exibição AllNotes, CollectionView lista todas as anotações, mas não permite que você selecione a mesma nota duas vezes. Há duas maneiras de o item permanecer selecionado: quando o usuário altera uma nota existente e quando o usuário navega à força para trás. O caso em que o usuário salva uma anotação é corrigido com a alteração de código na seção anterior que usa AllNotes.Move, para que você não precise se preocupar com esse caso.

O problema que você precisa resolver agora está relacionado à navegação. Não importa como o modo de exibição Allnotes é navegado, o NavigatedTo evento é gerado para a página. Este evento é uma oportunidade perfeita para forçar a desseleção do item selecionado no CollectionView.

No entanto, com o padrão MVVM sendo aplicado aqui, o modelo-de-visão não pode disparar algo diretamente na visão, como limpar o item selecionado após a nota ser salva. Então, como você faz isso acontecer? Uma boa implementação do padrão MVVM minimiza o code-behind na exibição. Há algumas maneiras diferentes de resolver esse problema para dar suporte ao padrão de separação MVVM. No entanto, também não há problema em colocar o código no code-behind da exibição, especialmente quando ele está diretamente vinculado à exibição. O MVVM tem muitos ótimos designs e conceitos que ajudam a compartimentalizar seu aplicativo, melhorando a manutenção e facilitando a adição de novos recursos. No entanto, em alguns casos, você pode descobrir que o MVVM incentiva o excesso de engenharia.

Não superengenhe uma solução para esse problema, e apenas use o evento NavigatedTo para limpar o item selecionado do CollectionView.

  1. No painel Gerenciador de Soluções do Visual Studio, clique duas vezes em Views\AllNotesPage.xaml.

  2. No XAML do <ContentPage>, adicione o evento 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. Você pode adicionar um manipulador de eventos padrão clicando com o botão direito do mouse no nome ContentPage_NavigatedTodo método do evento e selecionando Ir para Definição. Essa ação abre o Views\AllNotesPage.xaml.cs no editor de código.

  4. Substitua o código do manipulador de eventos pelo seguinte snippet:

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

    No XAML, o CollectionView recebeu o nome de notesCollection. Este código usa esse nome para acessar o CollectionView, e definir SelectedItem como null. O item selecionado é removido sempre que a página é acessada.

Agora, execute seu aplicativo. Tente navegar até uma anotação, pressione o botão Voltar e selecione a mesma nota uma segunda vez. O comportamento do aplicativo foi corrigido!

Explore o código. Explore o código deste tutorial.. Se você quiser baixar uma cópia do projeto concluído com a qual comparar seu código, baixe este projeto.

Seu aplicativo agora está usando padrões MVVM!

Próximas etapas

Os seguintes links fornecem mais informações relacionadas a alguns dos conceitos que você aprendeu neste tutorial: