Share via


Model-View-ViewModel (MVVM)

Dica

Esse conteúdo é um trecho do livro eletrônico, Padrões de Aplicativo Empresarial Usando .NETMAUI, disponível em .NET Docs ou em PDF para download gratuito que pode ser lido off-line.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

A experiência do desenvolvedor .NET MAUI normalmente envolve a criação de uma interface do usuário em XAML e, em seguida, a adição de code-behind que opera na 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 na interface do usuário e a dificuldade de testar esse código por unidade.

O padrão MVVM ajuda a separar de forma limpa a lógica de negócios e apresentação de um aplicativo da interface do usuário. 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. 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 MVVM

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

The MVVM pattern

Além de entender as responsabilidades de cada componente, também é importante entender como eles interagem. Em alto nível, a exibição "sabe sobre" o modelo de exibição e o modelo de exibição "sabe sobre" o modelo, mas o modelo não está ciente do modelo de exibição e o modelo de exibição não está ciente da exibição. Portanto, o modelo de exibição isola a exibição do modelo e permite que o modelo evolua independentemente da exibição.

Os benefícios de usar o padrão MVVM são os seguintes:

  • Se uma implementação de modelo existente encapsular a lógica de negócios existente, poderá ser difícil ou arriscado alterá-la. Nesse cenário, o modelo de exibição atua como um adaptador para as classes de modelo e impede que você faça alterações importantes no código do modelo.
  • Os desenvolvedores podem criar testes de unidade para o modelo de exibição e o modelo, sem usar o modo de exibição. Os testes de unidade para o modelo de exibição podem exercer exatamente a mesma funcionalidade usada pelo modo de exibição.
  • A interface do usuário do aplicativo pode ser reprojetada sem tocar no modelo de exibição e no código do modelo, desde que a exibição seja implementada inteiramente em XAML ou C#. Portanto, uma nova versão do modo de exibição deve funcionar com o modelo de exibição existente.
  • Designers e desenvolvedores podem trabalhar de forma independente e simultânea em seus componentes durante o desenvolvimento. Os designers podem se concentrar na exibição, enquanto os desenvolvedores podem trabalhar no modelo de exibição e nos componentes do modelo.

A chave para usar o MVVM está efetivamente no entendimento de como fatorar o código do aplicativo nas classes corretas e como as classes interagem. As seções a seguir discutem as responsabilidades de cada uma das classes no padrão MVVM.

Visualizar

A exibição é responsável por definir a estrutura, o layout e a aparência do que o usuário vê na tela. O ideal é que cada exibição seja 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.

Em um aplicativo .NET MAUI, uma exibição normalmente é uma classe ContentPage derivada ou ContentView derivada. No entanto, as exibições também podem ser representadas por um modelo de dados, que especifica os elementos da interface do usuário a serem usados para representar visualmente um objeto quando ele é exibido. Um modelo de dados como uma exibição não tem nenhum code-behind e foi projetado para associar a um tipo de modelo de exibição específico.

Dica

Evite habilitar e desabilitar elementos de interface do usuário no code-behind.

Verifique se os modelos de exibição são responsáveis por definir alterações de estado lógico que afetam alguns aspectos do display de exibição, como se um comando está disponível ou uma indicação de que uma operação está pendente. Portanto, habilite e desabilite os elementos da interface do usuário associando-se para exibir as propriedades do modelo, em vez de habilitá-los e desabilitá-los no code-behind.

Há várias opções para executar código no modelo de exibição em resposta às interações no modo de exibição, como um clique de botão ou seleção de item. Se um controle der suporte a comandos, a propriedade Command do controle poderá ser associada a dados a uma propriedade ICommand no modelo de exibição. Quando o comando do controle for invocado, o código no modelo de exibição será executado. Além dos comandos, os comportamentos podem ser anexados a um objeto na exibição e podem escutar um comando a ser invocado ou o evento a ser gerado. Em resposta, o comportamento pode invocar um ICommand no modelo de exibição ou um método no modelo de exibição.

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 fornecidos pelo modelo de exibição definem a funcionalidade a ser oferecida pela interface do usuário, mas a exibição determina como essa funcionalidade deve ser exibida.

Dica

Mantenha a interface do usuário responsiva com operações assíncronas.

Aplicativos de várias plataformas devem manter o thread da interface do usuário desbloqueado para melhorar a percepção de desempenho do usuário. Portanto, no modelo de exibição, use métodos assíncronos para operações de E/S e gere eventos para notificar as exibições de alterações de propriedade de forma assíncrona.

O modelo de exibição também é responsável por coordenar as interações da exibição com todas as classes de modelo necessárias. Normalmente, há uma relação um-para-muitos entre o modelo de exibição e as classes de modelo. O modelo de exibição pode optar por expor classes de modelo diretamente à exibição para que os controles na exibição possam associar dados diretamente a elas. Nesse caso, as classes de modelo precisarão ser projetadas para dar suporte à associação de dados e a eventos de notificação de alteração.

Cada modelo de exibição fornece dados de um modelo de uma forma que a exibiçã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 porque fornece propriedades às quais a exibição pode se associar. Por exemplo, o modelo de exibição pode combinar os valores de duas propriedades para facilitar a exibição pelo modo de exibição.

Dica

Centralize as conversões de dados em uma camada de conversão.

Também é possível usar conversores como uma camada de conversão de dados separada que fica entre o modelo de exibição e a exibição. Isso pode ser necessário, por exemplo, quando os dados exigem formatação especial que o modelo de exibição não fornece.

Para que o modelo de exibição participe da associação de dados bidirecional com o modo de exibição, suas propriedades devem gerar o PropertyChanged evento. Os modelos de exibição atendem a esse requisito implementando a interface INotifyPropertyChanged e gerando o evento PropertyChanged quando uma propriedade for alterada.

Para coleções, a exibição fácil ObservableCollection<T> é fornecida. Essa coleção implementa a notificação alterada de coleção, liberando o desenvolvedor de ter que implementar a interface INotifyCollectionChanged em coleções.

Modelar

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. Exemplos de objetos de modelo incluem objetos de transferência de dados (DTOs), Objetos CLR Antigos Simples (POCOs) e objetos de entidade e proxy gerados.

As classes de modelo normalmente são usadas em conjunto com serviços ou repositórios que encapsulam o acesso e o cache de dados.

Conectando modelos de exibição a exibições

Os modelos de exibição podem ser conectados a exibições usando os recursos de associação de dados do .NET MAUI. Há muitas abordagens que podem ser usadas para construir exibições e modelos de exibição e associá-los em runtime. Essas abordagens se enquadram em duas categorias, conhecidas como primeira composição de exibição, e primeira composição do modelo de exibição. Escolher entre a primeira composição de exibição e a primeira composição do modelo de exibição é um problema de preferência e complexidade. No entanto, todas as abordagens compartilham o mesmo objetivo, que a exibição tenha um modelo de exibição atribuído à propriedade BindingContext.

Com a primeira composição de exibição, o aplicativo é conceitualmente composto por exibições que se conectam aos modelos de exibição dos quais dependem. O principal benefício dessa abordagem é que ela facilita a construção de aplicativos testáveis de unidade e acoplados livremente porque os modelos de exibição não têm dependência nos modos de exibição em si. Também é fácil entender a estrutura do aplicativo seguindo sua estrutura visual, em vez de ter que acompanhar a execução de código para entender como as classes são criadas e associadas. Além disso, a primeira construção de exibição se alinha ao sistema de navegação do Microsoft Maui responsável por construir páginas quando ocorre a navegação, o que torna uma primeira composição do modelo de exibição complexa e desalinhada com a plataforma.

Com a primeira composição do modelo de exibição, o aplicativo é conceitualmente composto por modelos de exibição, com um serviço responsável por localizar a exibição de um modelo de exibição. A primeira composição do modelo de exibição parece mais natural para alguns desenvolvedores, pois a criação da exibição pode ser abstraída, permitindo que eles se concentrem na estrutura lógica de não interface do usuário do aplicativo. Além disso, permite que os modelos de exibição sejam criados por outros modelos de exibição. No entanto, essa abordagem geralmente é complexa e pode se tornar difícil entender como as várias partes do aplicativo são criadas e associadas.

Dica

Mantenha os modelos de exibição e exibições independentes.

A associação de exibições a uma propriedade em uma fonte de dados deve ser a dependência principal da exibição em seu modelo de exibição correspondente. Especificamente, não referencie tipos de exibição, como Button e ListView, de modelos de exibição. Seguindo os princípios descritos aqui, os modelos de exibição podem ser testados isoladamente, reduzindo, portanto, a probabilidade de defeitos de software limitando o escopo.

As seções a seguir discutem as principais abordagens para conectar modelos de exibição a exibições.

Criando um modelo de exibição declarativamente

A abordagem mais simples é que a exibição crie uma instância declarativamente no seu modelo de exibição correspondente em XAML. Quando a exibição for construída, o objeto do modelo de exibição correspondente também será construído. Essa abordagem é demonstrada no exemplo de código a seguir:

<ContentPage xmlns:local="clr-namespace:eShop">
    <ContentPage.BindingContext>
        <local:LoginViewModel />
    </ContentPage.BindingContext>
    <!-- Omitted for brevity... -->
</ContentPage>

Quando o ContentPage é criado, uma instância do LoginViewModel é construída automaticamente e definida como a do modo de exibição BindingContext.

Essa construção declarativa e atribuição do modelo de exibição pela exibição têm a vantagem de ser simples, mas têm a desvantagem de exigir um construtor padrão (sem parâmetros) no modelo de exibição.

Criando um modelo de exibição programaticamente

Uma exibição pode ter código no arquivo code-behind, resultando na atribuição do modelo de exibição à sua BindingContext propriedade. Isso geralmente é feito no construtor do modo de exibição, conforme mostrado no exemplo de código a seguir:

public LoginView()
{
    InitializeComponent();
    BindingContext = new LoginViewModel(navigationService);
}

A construção programática e a atribuição do modelo de exibição no code-behind da exibição têm a vantagem de serem simples. No entanto, a principal desvantagem dessa abordagem é que a exibição precisa fornecer ao modelo de exibição quaisquer dependências necessárias. O uso de um contêiner de injeção de dependência pode ajudar a manter o acoplamento flexível entre a exibição e o modelo de exibição. Para obter mais informações, consulte Injeção de dependência.

Atualizando exibições em resposta a alterações no modelo de exibição subjacente ou modelo

Todas as classes de modelo e modelo de exibição acessíveis a uma exibição devem implementar a interface [INotifyPropertyChanged. Implementar essa interface em um modelo de exibição ou classe de modelo permite que a classe forneça notificações de alteração para quaisquer controles associados a dados na exibição quando o valor da propriedade subjacente for alterado.

O aplicativo deve ser projetado para o uso correto da notificação de alteração de propriedade, atendendo aos seguintes requisitos:

  • Sempre gerar um evento PropertyChanged se o valor da propriedade pública for alterado. Não supor que a geração do evento PropertyChanged pode ser ignorada devido ao conhecimento de como a associação XAML ocorre.
  • Sempre gerar um evento PropertyChanged para quaisquer propriedades calculadas cujos valores são usados por outras propriedades no modelo de exibição ou modelo.
  • Sempre gerar o evento PropertyChanged no final do método que faz uma alteração de propriedade ou quando o objeto for conhecido por estar em um estado seguro. A geração do evento interrompe a operação invocando os manipuladores de evento de forma síncrona. Se isso acontecer no meio de uma operação, ele poderá expor o objeto a funções de retorno de chamada quando ele estiver em um estado não seguro e parcialmente atualizado. Além disso, é possível que alterações em cascata sejam disparadas por eventos PropertyChanged. As alterações em cascata geralmente exigem que as atualizações sejam concluídas antes que seja seguro executar a alteração em cascata.
  • Nunca gere um evento PropertyChanged se a propriedade não for alterada. Isso significa que você deve comparar os valores antigos e novos antes de gerar o evento PropertyChanged.
  • Nunca gere o evento PropertyChanged durante o constructo de um modelo de exibição se você estiver inicializando uma propriedade. Os controles associados a dados na exibição não terão assinatura para receber notificações de alteração neste momento.
  • Nunca gere mais de um evento PropertyChanged com o mesmo argumento de nome de propriedade dentro de uma única invocação síncrona de um método público de uma classe. Por exemplo, dada uma propriedade NumberOfItems, cujo repositório de backup é o campo _numberOfItems, se um método aumentar _numberOfItems cinquenta vezes durante a execução de um loop, ele só deverá gerar notificação de alteração de propriedade na propriedade NumberOfItems uma vez, depois que todo o trabalho for concluído. Para métodos assíncronos, gere o evento PropertyChangedpara um determinado nome de propriedade em cada segmento síncrono de uma cadeia de continuação assíncrona.

Uma maneira simples de fornecer essa funcionalidade seria criar uma extensão da classe BindableObject. Neste exemplo, a classe ExtendedBindableObject fornece notificações de alteração, que são mostradas no exemplo de código a seguir:

public abstract class ExtendedBindableObject : BindableObject
{
    public void RaisePropertyChanged<T>(Expression<Func<T>> property)
    {
        var name = GetMemberInfo(property).Name;
        OnPropertyChanged(name);
    }

    private MemberInfo GetMemberInfo(Expression expression)
    {
        // Omitted for brevity ...
    }
}

A classe do .NET MAUIBindableObjectimplementa a interface INotifyPropertyChanged e fornece um método OnPropertyChanged. A classe ExtendedBindableObject fornece o método RaisePropertyChanged para invocar a notificação de alteração de propriedade e, ao fazê-lo, usa a funcionalidade fornecida pela classe BindableObject.

As classes de modelo de exibição podem derivar da classe ExtendedBindableObject. Portanto, cada classe de modelo de exibição usa o método RaisePropertyChanged na classe ExtendedBindableObject para fornecer notificação de alteração de propriedade. O exemplo de código a seguir mostra como o aplicativo multiplataforma eShopOnContainers invoca a notificação de alteração de propriedade usando uma expressão lambda:

public bool IsLogin
{
    get => _isLogin;
    set
    {
        _isLogin = value;
        RaisePropertyChanged(() => IsLogin);
    }
}

O uso de uma expressão lambda dessa forma envolve um pequeno custo de desempenho porque a expressão lambda precisa ser avaliada para cada chamada. Embora o custo de desempenho seja pequeno e normalmente não afete um aplicativo, os custos podem se acumular quando há muitas notificações de alteração. No entanto, o benefício dessa abordagem é que ela fornece suporte de segurança de tipo de tempo de compilação e refatoração ao renomear propriedades.

Estruturas MVVM

O padrão MVVM é bem estabelecido no .NET e a comunidade criou muitas estruturas que ajudam a facilitar esse desenvolvimento. Cada estrutura fornece um conjunto diferente de recursos, mas é padrão para eles fornecer um modelo de exibição comum com uma implementação da interface INotifyPropertyChanged. Os recursos adicionais de estruturas MVVM incluem comandos personalizados, auxiliares de navegação, componentes do localizador de serviço/injeção de dependência e integração da plataforma de interface do usuário. Embora não seja necessário usar essas estruturas, elas podem acelerar e padronizar seu desenvolvimento. O aplicativo multiplataforma eShopOnContainers usa o Kit de Ferramentas MVVM da Comunidade do .NET. Ao escolher uma estrutura, você deve considerar as necessidades do aplicativo e os pontos fortes da sua equipe. A lista abaixo inclui algumas das estruturas MVVM mais comuns para .NET MAUI.

Interação da interface do usuário usando comandos e comportamentos

Em aplicativos de várias plataformas, as ações normalmente são invocadas em resposta a uma ação do usuário, como um clique de botão, que pode ser implementado criando um manipulador de eventos no arquivo code-behind. No entanto, no padrão MVVM, a responsabilidade de implementar a ação é do modelo de exibição e a colocação de código no code-behind deve ser evitada.

Os comandos fornecem uma maneira conveniente de representar ações que podem ser associadas a controles na interface do usuário. Eles encapsulam o código que implementa a ação e ajudam a mantê-la separada de sua representação visual na exibição. Dessa forma, seus modelos de exibição se tornam mais portáteis para novas plataformas, pois eles não têm uma dependência direta de eventos fornecidos pela estrutura de interface do usuário da plataforma. O .NET MAUI inclui controles que podem ser conectados declarativamente a um comando e esses controles invocarão o comando quando o usuário interagir com o controle.

Os comportamentos também permitem que os controles sejam conectados declarativamente a um comando. No entanto, os comportamentos podem ser usados para invocar uma ação associada a um intervalo de eventos gerados por um controle. Portanto, os comportamentos abordam muitos dos mesmos cenários que os controles habilitados para comando, ao mesmo tempo em que fornecem um maior grau de flexibilidade e controle. Além disso, os comportamentos também podem ser usados para associar objetos de comando ou métodos a controles que não foram especificamente projetados para interagir com comandos.

Implementando comandos

Os modelos de exibição normalmente expõem propriedades públicas, para associação do modo de exibição, que implementam a ICommand interface. Muitos controles e gestos do .NET MAUI fornecem uma Command propriedade, que pode ser associada a um ICommand objeto fornecido pelo modelo de exibição. O controle de botão é um dos controles mais comumente usados, fornecendo uma propriedade de comando que é executada quando o botão é clicado.

Observação

Embora seja possível expor a implementação real da interface ICommand que seu modelo de exibição utiliza (por exemplo, Command<T> ou RelayCommand), é recomendável expor seus comandos publicamente como ICommand. Dessa forma, se você precisar alterar a implementação posteriormente, ela poderá ser facilmente trocada.

A interface ICommand define um método Execute, que encapsula a própria operação, um método CanExecute, que indica se o comando pode ser invocado e um evento CanExecuteChanged que ocorre quando ocorrem alterações que afetam se o comando deve ser executado. Na maioria dos casos, forneceremos apenas o método Execute para nossos comandos. Para obter uma visão geral mais detalhada de ICommand, consulte a documentação de comando para .NET MAUI.

Fornecidas com .NET MAUI são as classes Command e Command<T> que implementam a interface ICommand, onde T é o tipo dos argumentos para Execute e CanExecute. Command e Command<T> são implementações básicas que fornecem o conjunto mínimo de funcionalidades necessárias para a interfaceICommand.

Observação

Muitas estruturas MVVM oferecem implementações mais avançadas de recursos da interface ICommand.

O constructo Command ou Command<T> requer que um objeto de retorno de chamada Action seja chamado quando o método ICommand.Execute for invocado. O método CanExecute é um parâmetro de constructo opcional e é um Func que retorna um bool.

O aplicativo multiplataforma eShopOnContainers usa RelayCommand e AsyncRelayCommand. O principal benefício para aplicativos modernos é que AsyncRelayCommand fornece melhor funcionalidade para operações assíncronas.

O código a seguir mostra como uma instância Command, que representa um comando de registro, é construída especificando um delegado para o método de modelo de exibição do Registro:

public ICommand RegisterCommand { get; }

O comando é exposto à exibição por meio de uma propriedade que retorna uma referência a um ICommand. Quando o método Execute é chamado no objeto Command, ele simplesmente encaminha a chamada para o método no modelo de exibição por meio do delegado especificado no constructo Command. Um método assíncrono pode ser invocado por um comando usando as palavras-chave async e await ao especificar o delegado do comando Execute. Isso indica que o retorno de chamada é um Task e deve ser aguardado. Por exemplo, o código a seguir mostra como uma instância ICommand, que representa um comando de entrada, é construída especificando um delegado para o método do modelo de exibição SignInAsync:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

Os parâmetros podem ser passados para as ações Execute e CanExecute usando a classe AsyncRelayCommand<T> para instanciar o comando. Por exemplo, o código a seguir mostra como uma instância AsyncRelayCommand<T> é usada para indicar que o método NavigateAsync exigirá um argumento do tipo cadeia de caracteres:

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

Nas classes RelayCommande RelayCommand<T>, o delegado para o método CanExecute em cada constructo é opcional. Se um delegado não for especificado, o Command retornará true para CanExecute. No entanto, o modelo de exibição pode indicar uma alteração no status do comandoCanExecute chamando o método ChangeCanExecute no objeto Command. Isso faz com que o evento CanExecuteChanged seja gerado. Todos os controles de interface do usuário associados ao comando atualizarão o status habilitado para refletir a disponibilidade do comando associado a dados.

Invocando comandos de uma exibição

O exemplo de código a seguir mostra como Grid no LoginView se associa ao RegisterCommand na classe LoginViewModel usando uma instância TapGestureRecognizer:

<Grid Grid.Column="1" HorizontalOptions="Center">
    <Label Text="REGISTER" TextColor="Gray"/>
    <Grid.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
    </Grid.GestureRecognizers>
</Grid>

Um parâmetro de comando também pode ser definido opcionalmente usando a propriedade CommandParameter. O tipo do argumento esperado é especificado nos métodos de destino Execute e CanExecute. O TapGestureRecognizer invocará automaticamente o comando de destino quando o usuário interagir com o controle anexado. O CommandParameter, se fornecido, será passado como o argumento para o delegado Execute do comando.

Implementando comportamentos

Os comportamentos permitem adicionar a funcionalidade a controles de interface do usuário sem precisar dividi-los em subclasse. Em vez disso, a funcionalidade é implementada em uma classe de comportamento e anexada ao controle como se fizesse parte do próprio controle. Os comportamentos permitem que você implemente código que normalmente precisaria escrever como code-behind, porque interage diretamente com a API do controle, de forma que pode ser anexado concisamente ao controle e empacotado para reutilização em mais de uma exibição ou aplicativo. No contexto do MVVM, os comportamentos são uma abordagem útil para conectar controles a comandos.

Um comportamento anexado a um controle por meio de propriedades anexadas é conhecido como um comportamento anexado. O comportamento pode então usar a API exposta do elemento ao qual ele está anexado para adicionar funcionalidade a esse controle, ou outros controles, na árvore visual do modo de exibição.

Um comportamento de .NETMAUI é uma classe que deriva da classe Behavior ou Behavior<T> , em que T é o tipo do controle ao qual o comportamento deve ser aplicado. Essas classes fornecem os métodos OnAttachedTo e OnDetachingFrom, que devem ser substituídos para fornecer lógica que será executada quando o comportamento for anexado e desanexado dos controles.

No aplicativo multiplataforma eShopOnContainers, a classe BindableBehavior<T> deriva da classe Behavior<T>. A finalidade da classe BindableBehavior<T> é fornecer uma classe base para todos os comportamentos do .NET MAUI que exigem que o BindingContext do comportamento seja definido como o controle anexado.

A classe BindableBehavior<T> fornece um método OnAttachedTo substituível que define o BindingContext do comportamento e um método OnDetachingFrom substituível que limpa o BindingContext.

O aplicativo multiplataforma eShopOnContainers inclui uma classe EventToCommandBehavior que é fornecida pelo MAUIKit de ferramentas da Comunidade. EventToCommandBehavior executa um comando em resposta a um evento que está ocorrendo. Essa classe deriva da classe BaseBehavior<View> para que o comportamento possa se associar e executar um ICommand especificado por uma propriedade Command quando o comportamento for consumido. O exemplo de código a seguir mostra a classe EventToCommandBehavior:

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

Os métodos OnAttachedTo e OnDetachingFrom são usados para registrar e cancelar o registro de um manipulador de eventos para o evento definido na propriedade EventName. Em seguida, quando o evento é acionado, o método OnTriggerHandled é invocado, que executa o comando.

A vantagem de usar EventToCommandBehavior para executar um comando quando um evento é acionado é que os comandos podem ser associados a controles que não foram projetados para interagir com comandos. Além disso, isso move o código de manipulação de eventos para exibir modelos, em que ele pode ser testado por unidade.

Invocando comportamentos de uma exibição

O EventToCommandBehavior é particularmente útil para anexar um comando a um controle que não dá suporte a comandos. Por exemplo, o LoginView usa o EventToCommandBehavior para executar o ValidateCommand quando o usuário altera o valor de sua senha, conforme mostrado no código a seguir:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

Em runtime, EventToCommandBehavior responderá à interação com Entry. Quando um usuário digita no campo Entry, o evento TextChanged será acionado, que executará o ValidateCommand no LoginViewModel. Por padrão, os argumentos do evento para o evento serão passados para o comando. Se necessário, a propriedade EventArgsConverter pode ser usada para converter o EventArgs fornecido pelo evento em um valor que o comando espera como entrada.

Para obter mais informações sobre comportamentos, consulte Comportamentos na Central do Desenvolvedor do .NET MAUI.

Resumo

O padrão Model-View-ViewModel (MVVM) ajuda a separar de forma limpa a lógica de negócios e apresentação de um aplicativo da interface do usuário. 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. 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.

Usando o padrão MVVM, a interface do usuário do aplicativo e a apresentação subjacente e a lógica de negócios são separadas em três classes separadas: a exibição, que encapsula a interface do usuário e a lógica da interface do usuário; o modelo de exibição, que encapsula a lógica e o estado da apresentação; e o modelo, que encapsula a lógica de negócios e os dados do aplicativo.