Ler em inglês

Partilhar via


Model-View-ViewModel (MVVM)

Gorjeta

Este conteúdo é um excerto do eBook, Enterprise Application Patterns Using .NET MAUI, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

Padrões de aplicativos corporativos usando a miniatura da capa do eBook .NET MAUI .

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 estreito entre os controles da 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 de unidade.

O padrão MVVM ajuda a separar claramente a lógica de negócios e apresentação de um aplicativo de sua interface do usuário (UI). Manter uma separação clara entre a lógica do aplicativo e a interface do usuário ajuda a resolver vários problemas de desenvolvimento e torna um aplicativo mais fácil de testar, manter e evoluir. 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 mais facilmente ao desenvolver suas respetivas 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 serve um propósito distinto. O diagrama abaixo mostra as relações entre os três componentes.

O padrão MVVM

Além de entender as responsabilidades de cada componente, também é importante entender como eles interagem. Em um nível alto, o modo de 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 do modo de 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, pode 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 grandes alterações 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 redesenhada 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 efetivamente está em entender 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.

Vista

A visualização é responsável por definir a estrutura, layout e 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.

Em um aplicativo .NET MAUI , um modo de exibição é normalmente uma ContentPageclasse derivada ou ContentViewderivada. 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 modo de exibição não tem nenhum code-behind e foi projetado para vincular a um tipo de modelo de exibição específico.

Gorjeta

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

Certifique-se de que os modelos de exibição são responsáveis por definir alterações de estado lógico que afetam alguns aspetos da exibição do modo 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 vinculando-os 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 a interações no modo de exibição, como um clique no botão ou seleção de item. Se um controle oferecer suporte a comandos, a propriedade Command do controle pode ser vinculada a dados a uma propriedade ICommand no modelo de exibição. Quando o comando do controle é 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 os dados podem ser vinculados 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 o modo de exibição determina como essa funcionalidade deve ser exibida.

Gorjeta

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

Os aplicativos multiplataforma devem manter o thread da interface do usuário desbloqueado para melhorar a perceçã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 de forma assíncrona as exibições de alterações de propriedade.

O modelo de exibição também é responsável por coordenar as interações do modo de exibição com quaisquer 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 se vincular diretamente a elas. Nesse caso, as classes de modelo precisarão ser projetadas para oferecer suporte a eventos de notificação de alteração e vinculação de dados.

Cada modelo de exibição fornece dados de um modelo em um formato que o modo de 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 o modo de exibição pode se associar. Por exemplo, o modelo de exibição pode combinar os valores de duas propriedades para facilitar a exibição pela exibição.

Gorjeta

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 satisfazem esse requisito implementando a INotifyPropertyChanged interface e gerando o PropertyChanged evento quando uma propriedade é alterada.

Para coleções, a vista amigável ObservableCollection<T> é fornecida. Esta coleção implementa a notificação de alteração de coleção, aliviando o desenvolvedor de ter que implementar a INotifyCollectionChanged interface em coleções.

Modelo

As classes de modelo são classes não visuais que encapsulam os dados do aplicativo. Portanto, o modelo pode ser pensado como representando o modelo de domínio do aplicativo, que geralmente inclui um modelo de dados juntamente 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 são normalmente usadas em conjunto com serviços ou repositórios que encapsulam o acesso a dados e o cache.

Ligar modelos de vista a vistas

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

Com a primeira composição de exibição, o aplicativo é conceitualmente composto de modos de exibição 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 de unidade acoplados de forma flexível e testáveis, porque os modelos de exibição não dependem das próprias exibições. Também é fácil entender a estrutura do aplicativo seguindo sua estrutura visual, em vez de ter que acompanhar a execução do código para entender como as classes são criadas e associadas. Além disso, a primeira construção de visualização está alinhada com o sistema de navegação da Microsoft Maui, que é responsável pela construção de páginas quando a navegação ocorre, o que torna a primeira composição de um modelo de visualização complexa e desalinhada com a plataforma.

Com a primeira composição do modelo de visualizaçã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, uma vez que a criação de exibição pode ser abstrata, permitindo que eles se concentrem na estrutura lógica não-UI do aplicativo. Além disso, permite que modelos de visualização sejam criados por outros modelos de visualizaçã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.

Gorjeta

Mantenha modelos de visualização e vistas independentes.

A associação de modos de exibição a uma propriedade em uma fonte de dados deve ser a principal dependência do modo de exibição em seu modelo de exibição correspondente. Especificamente, não faça referência a tipos de exibição, como Button e ListView, a partir de modelos de exibição. Seguindo os princípios descritos aqui, os modelos de exibição podem ser testados isoladamente, reduzindo assim a probabilidade de defeitos de software ao limitar o escopo.

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

Criando um modelo de exibição declarativamente

A abordagem mais simples é que o modo de exibição instancie declarativamente seu modelo de exibição correspondente em XAML. Quando a exibição é construída, o objeto de 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 é automaticamente construída e definida como o modo de LoginViewModel BindingContextexibição .

Essa construção declarativa e atribuição do modelo de exibição pelo modo de exibição tem a vantagem de ser simples, mas tem a desvantagem de exigir um construtor padrão (sem parâmetro) 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 é realizado 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 dentro do code-behind da exibição tem a vantagem de ser simples. No entanto, a principal desvantagem dessa abordagem é que a exibição precisa fornecer ao modelo de exibição todas as 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 o modelo de exibição e o modelo de exibição. Para obter mais informações, consulte Injeção de dependência.

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

Todos os modelos de exibição e classes de modelo acessíveis a um modo de exibição devem implementar a INotifyPropertyChanged interface. A implementação dessa 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 ligados a dados na exibição quando o valor da propriedade subjacente for alterado.

Os aplicativos devem ser projetados para o uso correto da notificação de alteração de propriedade, atendendo aos seguintes requisitos:

  • Sempre levantando um PropertyChanged evento se o valor de um imóvel público mudar. Não assuma que o aumento do evento possa ser ignorado devido ao conhecimento de como ocorre a PropertyChanged associação XAML.
  • Sempre gerando um PropertyChanged evento para quaisquer propriedades calculadas cujos valores são usados por outras propriedades no modelo ou modelo de exibição.
  • Sempre gerando o PropertyChanged evento no final do método que faz uma alteração de propriedade ou quando o objeto é conhecido por estar em um estado seguro. O aumento do evento interrompe a operação invocando os manipuladores do evento de forma síncrona. Se isso acontecer no meio de uma operação, ele pode expor o objeto a funções de retorno de chamada quando ele estiver em um estado inseguro e parcialmente atualizado. Além disso, é possível que alterações em cascata sejam acionadas por PropertyChanged eventos. As alterações em cascata geralmente exigem que as atualizações sejam concluídas antes que a alteração em cascata seja segura para ser executada.
  • Nunca levante um PropertyChanged evento se o imóvel não mudar. Isso significa que você deve comparar os valores antigos e novos antes de levantar o PropertyChanged evento.
  • Nunca gerar o evento durante o PropertyChanged construtor de um modelo de exibição se você estiver inicializando uma propriedade. Os controles ligados a dados na exibição não terão se inscrito para receber notificações de alteração neste momento.
  • Nunca gerando mais de um PropertyChanged evento 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 NumberOfItems propriedade cujo armazenamento de suporte é o _numberOfItems campo, se um método incrementar _numberOfItems cinquenta vezes durante a execução de um loop, ele só deve gerar notificação NumberOfItems de alteração de propriedade na propriedade uma vez, depois que todo o trabalho for concluído. Para métodos assíncronos, gere o evento para um determinado nome de propriedade em cada segmento síncrono de uma cadeia de continuação assíncrona PropertyChanged .

Uma maneira simples de fornecer essa funcionalidade seria criar uma extensão da BindableObject classe. Neste exemplo, a ExtendedBindableObject classe fornece notificações de alteração, que é mostrado 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 MAUIimplementa a INotifyPropertyChanged interface e fornece um OnPropertyChanged BindableObject método. A ExtendedBindableObject classe fornece o método para invocar a RaisePropertyChanged notificação de alteração de propriedade e, ao fazer isso, usa a funcionalidade fornecida pela BindableObject classe.

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

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

Usar uma expressão lambda dessa maneira 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 segurança de tipo em tempo de compilação e suporte à refatoração ao renomear propriedades.

Estruturas MVVM

O padrão MVVM está 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 INotifyPropertyChanged interface. Recursos adicionais das estruturas MVVM incluem comandos personalizados, auxiliares de navegação, componentes de injeção de dependência/localizador de serviços e integração de 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 eShop usa o .NET Community MVVM Toolkit. Ao escolher uma estrutura, você deve considerar as necessidades do seu 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 multiplataforma, as ações geralmente são invocadas em resposta a uma ação do usuário, como um clique no botão, que pode ser implementada criando um manipulador de eventos no arquivo code-behind. No entanto, no padrão MVVM, a responsabilidade pela implementação da 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 vinculadas a controles na interface do usuário. Eles encapsulam o código que implementa a ação e ajudam a mantê-lo dissociado de sua representação visual na exibição. Dessa forma, seus modelos de exibição se tornam mais portáteis para novas plataformas, pois 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 uma variedade 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 ou métodos de comando 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 vinculação a partir da exibição, que implementam a ICommand interface. Muitos controles e gestos .NET MAUI fornecem uma Command propriedade, que pode ser dados vinculados a um ICommand objeto fornecido pelo modelo de exibição. O controle button é um dos controles mais usados, fornecendo uma propriedade command que é executada quando o botão é clicado.

Nota

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

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

Fornecido com .NET MAUI são as Command classes e Command<T> que implementam a ICommand interface, 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 ICommand interface.

Nota

Muitas estruturas MVVM oferecem implementações mais ricas em recursos da ICommand interface.

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

A aplicação multiplataforma eShop utiliza o RelayCommand e o AsyncRelayCommand. O principal benefício para aplicativos modernos é que o fornece melhor funcionalidade para operações assíncronas AsyncRelayCommand .

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

public ICommand RegisterCommand { get; }

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

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

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

public ICommand NavigateCommand { get; }

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

Em ambas as RelayCommand classes and RelayCommand<T> , o delegado ao CanExecute método em cada construtor é 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 CanExecute comando chamando o ChangeCanExecute método no Command objeto. Isso faz com que o CanExecuteChanged evento seja levantado. Todos os controles de interface do usuário vinculados ao comando atualizarão seu status habilitado para refletir a disponibilidade do comando vinculado a dados.

Invocar comandos a partir de uma vista

O exemplo de código a seguir mostra como a Grid in the LoginView se liga ao RegisterCommand in the LoginViewModel class usando uma TapGestureRecognizer instância:

<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 CommandParameter propriedade. O tipo do argumento esperado é especificado nos Execute métodos e CanExecute target. O TapGestureRecognizer invocará automaticamente o comando target quando o usuário interage com o controle anexado. O CommandParameter, se fornecido, será passado como argumento para o delegado Executar do comando.

Implementando comportamentos

Os comportamentos permitem que a funcionalidade seja adicionada aos controles da interface do usuário sem a necessidade de subclassificá-los. Em vez disso, a funcionalidade é implementada em uma classe de comportamento e anexada ao controle como se fosse parte do próprio controle. Os comportamentos permitem que você implemente um código que normalmente teria que escrever como code-behind, porque ele interage diretamente com a API do controle, de tal forma que pode ser anexado de forma concisa 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 da exibição.

Um comportamento .NET MAUI é uma classe que deriva da Behavior classe ou Behavior<T> , onde T é o tipo do controle ao qual o comportamento deve se aplicar. Essas classes fornecem OnAttachedTo e OnDetachingFrom métodos, que devem ser substituídos para fornecer lógica que será executada quando o comportamento é anexado e separado dos controles.

No aplicativo multiplataforma eShop, a BindableBehavior<T> classe deriva da Behavior<T> classe. O objetivo da BindableBehavior<T> classe é fornecer uma classe base para comportamentos .NET MAUI que exigem que o BindingContext do comportamento seja definido para o controle anexado.

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

O aplicativo multiplataforma eShop inclui uma classe EventToCommandBehavior que é fornecida pelo kit de ferramentas da MAUI Comunidade. EventToCommandBehavior Executa um comando em resposta a um evento que ocorre. Essa classe deriva da BaseBehavior<View> classe para que o comportamento possa se vincular e executar um ICommand especificado por uma Command propriedade quando o comportamento é consumido. O exemplo de código a seguir mostra a EventToCommandBehavior classe:

/// <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 OnAttachedTo métodos e OnDetachingFrom são usados para registrar e cancelar o registro de um manipulador de eventos para o evento definido na EventName propriedade. Em seguida, quando o evento é acionado, o OnTriggerHandled método é invocado, que executa o comando.

A vantagem de usar o para executar um comando quando um evento é acionado é que os EventToCommandBehavior 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, onde pode ser testado em unidade.

Invocar comportamentos a partir de uma vista

O EventToCommandBehavior é particularmente útil para anexar um comando a um controle que não suporta 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>

No tempo de execução, o EventToCommandBehavior responderá à interação com o Entry. Quando um usuário digita no Entry campo, o TextChanged evento será acionado, o que executará o ValidateCommand no LoginViewModel. Por padrão, os argumentos de evento para o evento são passados para o comando. Se necessário, a EventArgsConverter propriedade 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 no .NET MAUI Developer Center.

Resumo

O padrão Model-View-ViewModel (MVVM) ajuda a separar claramente a lógica de negócios e apresentação de um aplicativo de sua interface do usuário (UI). Manter uma separação clara entre a lógica do aplicativo e a interface do usuário ajuda a resolver vários problemas de desenvolvimento e torna um aplicativo mais fácil de testar, manter e evoluir. 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 mais facilmente ao desenvolver suas respetivas 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 visualizaçã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.