O padrão Model-View-ViewModel

Observação

Este e-book foi publicado na primavera de 2017 e não foi atualizado desde então. Há muito no livro que permanece valioso, mas parte do material está desatualizado.

A Xamarin.Forms experiência do desenvolvedor 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. À medida que os aplicativos são modificados e crescem em tamanho e escopo, podem surgir problemas complexos de manutenção. 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 (Model-View-ViewModel) ajuda a separar claramente a lógica de negócios e apresentação de um aplicativo de sua interface do usuário (interface do usuário). 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 pode tornar um aplicativo mais fácil de testar, manter e evoluir. Ele também pode melhorar muito 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 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. A Figura 2-1 mostra as relações entre os três componentes.

O padrão MVVM

Figura 2-1: O padrão MVVM

Além de entender as responsabilidades de cada componente, também é importante entender como eles interagem uns com os outros. 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 houver uma implementação de modelo existente que encapsule 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 permite que você evite fazer 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 código, desde que a exibição seja implementada inteiramente em XAML. 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 processo de 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 de forma eficaz está em entender como fatorar o código do aplicativo nas classes corretas e entender 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 Xamarin.Forms aplicativo, uma exibição normalmente é uma classe derivada Pageou 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 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. Certifique-se de que os modelos de exibição sejam responsáveis por definir alterações de estado lógico que afetem alguns aspectos da exibição da 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 do Command controle poderá ser associada a dados a uma ICommand propriedade 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 um 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. Os aplicativos móveis 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 pela 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 vista a vistas

Os modelos de exibição podem ser conectados a exibições usando os recursos de vinculação de dados do Xamarin.Forms. 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 construção do modo de exibição primeiro se alinha ao sistema de navegação responsável pela construção de páginas quando a navegação ocorre, o Xamarin.Forms 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 de modelos de exibição, com um serviço sendo 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 faça referência a tipos de vista, como Button e ListView, de modelos de vista. 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>  
    ...  
</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 que resulta 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.

Criando uma exibição definida como um modelo de dados

Uma exibição pode ser definida como um modelo de dados e associada a um tipo de modelo de exibição. Os modelos de dados podem ser definidos como recursos ou podem ser definidos embutidos no controle que exibirá o modelo de exibição. O conteúdo do controle é a instância do modelo de exibição e o modelo de dados é usado para representá-lo visualmente. Essa técnica é um exemplo de uma situação em que o modelo de exibição é instanciado primeiro, seguido pela criação da exibição.

Criando automaticamente um modelo de vista com um localizador de modelo de vista

Um localizador de modelo de exibição é uma classe personalizada que gerencia a instanciação de modelos de exibição e sua associação a exibições. No aplicativo móvel eShopOnContainers, a ViewModelLocator classe tem uma propriedade anexada, AutoWireViewModel, que é usada para associar modelos de exibição a exibições. No XAML do modo de exibição, essa propriedade anexada é definida como true para indicar que o modelo de exibição deve ser conectado automaticamente ao modo de exibição, conforme mostrado no exemplo de código a seguir:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

A AutoWireViewModel propriedade é uma propriedade associável que é inicializada como false e, quando seu valor é alterado, o OnAutoWireViewModelChanged manipulador de eventos é chamado. Esse método resolve o modelo de exibição para a exibição. O exemplo de código a seguir mostra como isso é feito:

private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)  
{  
    var view = bindable as Element;  
    if (view == null)  
    {  
        return;  
    }  

    var viewType = view.GetType();  
    var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");  
    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;  
    var viewModelName = string.Format(  
        CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);  

    var viewModelType = Type.GetType(viewModelName);  
    if (viewModelType == null)  
    {  
        return;  
    }  
    var viewModel = _container.Resolve(viewModelType);  
    view.BindingContext = viewModel;  
}

O OnAutoWireViewModelChanged método tenta resolver o modelo de exibição usando uma abordagem baseada em convenção. Esta convenção pressupõe que:

  • Os modelos de vista estão na mesma montagem que os tipos de vista.
  • As visualizações estão em um arquivo . Exibe o namespace filho.
  • Os modelos de vista estão em um arquivo . Namespace filho ViewModels.
  • Os nomes do modelo de exibição correspondem aos nomes de exibição e terminam com "ViewModel".

Por fim, o OnAutoWireViewModelChanged método define o BindingContext do tipo de exibição como o tipo de modelo de exibição resolvido. Para obter mais informações sobre como resolver o tipo de modelo de exibição, consulte Resolução.

Essa abordagem tem a vantagem de que um aplicativo tem uma única classe responsável pela instanciação de modelos de exibição e sua conexão com exibições.

Dica

Use um localizador de modelo de exibição para facilitar a substituição. Um localizador de modelo de exibição também pode ser usado como um ponto de substituição para implementações alternativas de dependências, como para testes de unidade ou dados de tempo de design.

Atualizando exibições em resposta a alterações no modelo de exibição subjacente ou no 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.

Os aplicativos devem ser arquitetados 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.

O aplicativo móvel eShopOnContainers usa a ExtendedBindableObject classe para fornecer notificações de alteração, 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)  
    {  
        ...  
    }  
}

A classe do BindableObject Xamarin.Form implementa a INotifyPropertyChanged interface e fornece um OnPropertyChanged método. 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.

Cada classe de modelo de exibição no aplicativo móvel eShopOnContainers deriva da ViewModelBase classe, que, por sua vez, deriva da ExtendedBindableObject classe. 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 móvel eShopOnContainers invoca a notificação de alteração de propriedade usando uma expressão lambda:

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

Observe que o uso de 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 ser acumulados 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.

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

Em aplicativos móveis, 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 implementada 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ê-lo desacoplado de sua representação visual na exibição. Xamarin.Forms 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 de comando, para associação da exibição, que são instâncias de objeto que implementam a ICommand interface. Vários controles fornecem uma Command propriedade, que pode ser associada a dados a um ICommand objeto fornecido pelo modelo de Xamarin.Forms exibição. 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. As Command classes and Command<T> , fornecidas por Xamarin.Forms, implementam a ICommand interface, onde T é o tipo dos argumentos para Execute e CanExecute.

Em um modelo de exibição, deve haver um objeto do tipo Command ou Command<T> para cada propriedade pública no modelo de exibição do tipo ICommand. O Command construtor or Command<T> requer um Action objeto de retorno de chamada 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.

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

public ICommand RegisterCommand => new Command(Register);

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 async palavras-chave and 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 Command 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 => new Command(async () => await SignInAsync());

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

public ICommand NavigateCommand => new Command<string>(NavigateAsync);

Nas classes Commande Command<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 na interface do usuário associados ao comando atualizarão seu status habilitado para refletir a disponibilidade do comando associado a dados.

Chamando 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 parâmetro de comando, se fornecido, será passado como o argumento para o delegado do Execute 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 o código que normalmente teria que escrever como code-behind, pois ele interage diretamente com a API do controle, de forma que possa ser anexado de forma concisa ao controle e empacotado para reutilização em mais de um modo de 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. O aplicativo móvel eShopOnContainers contém a LineColorBehavior classe, que é um comportamento anexado. Para obter mais informações sobre esse comportamento, consulte Exibindo erros de validação.

Um Xamarin.Forms comportamento é uma classe que deriva da Behavior classe or Behavior<T> , onde T é o tipo de 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 móvel eShopOnContainers, a BindableBehavior<T> classe deriva da Behavior<T> classe. A finalidade da BindableBehavior<T> classe é fornecer uma classe base para Xamarin.Forms comportamentos que exigem que o BindingContext 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. Além disso, a classe armazena uma referência ao controle anexado na propriedade AssociatedObject.

O aplicativo móvel eShopOnContainers inclui uma EventToCommandBehavior classe, que executa um comando em resposta a um evento que ocorre. Essa classe deriva da classe BindableBehavior<T> 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:

public class EventToCommandBehavior : BindableBehavior<View>  
{  
    ...  
    protected override void OnAttachedTo(View visualElement)  
    {  
        base.OnAttachedTo(visualElement);  

        var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();  
        if (events.Any())  
        {  
            _eventInfo = events.FirstOrDefault(e => e.Name == EventName);  
            if (_eventInfo == null)  
                throw new ArgumentException(string.Format(  
                        "EventToCommand: Can't find any event named '{0}' on attached type",   
                        EventName));  

            AddEventHandler(_eventInfo, AssociatedObject, OnFired);  
        }  
    }  

    protected override void OnDetachingFrom(View view)  
    {  
        if (_handler != null)  
            _eventInfo.RemoveEventHandler(AssociatedObject, _handler);  

        base.OnDetachingFrom(view);  
    }  

    private void AddEventHandler(  
            EventInfo eventInfo, object item, Action<object, EventArgs> action)  
    {  
        ...  
    }  

    private void OnFired(object sender, EventArgs eventArgs)  
    {  
        ...  
    }  
}

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 OnFired é 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 ProfileView usa o EventToCommandBehavior para executar o OrderDetailCommand quando o ItemTapped evento é acionado no ListView que lista os pedidos do usuário, conforme mostrado no código a seguir:

<ListView>  
    <ListView.Behaviors>  
        <behaviors:EventToCommandBehavior             
            EventName="ItemTapped"  
            Command="{Binding OrderDetailCommand}"  
            EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />  
    </ListView.Behaviors>  
    ...  
</ListView>

Em runtime, EventToCommandBehavior responderá à interação com ListView. Quando um item é selecionado no ListView, o ItemTapped evento será disparado, que executará o OrderDetailCommand no ProfileViewModel. Por padrão, os argumentos do evento para o evento serão passados para o comando. Esses dados são convertidos à medida que são passados entre a origem e o destino pelo conversor especificado na EventArgsConverter propriedade, que retorna o Item ListView do .ItemTappedEventArgs Portanto, quando o OrderDetailCommand é executado, o selecionado Order é passado como parâmetro para a Ação cadastrada.

Para obter mais informações sobre comportamentos, consulte Comportamentos.

Resumo

O padrão MVVM (Model-View-ViewModel) ajuda a separar claramente a lógica de negócios e apresentação de um aplicativo de sua interface do usuário (interface do usuário). 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 pode tornar um aplicativo mais fácil de testar, manter e evoluir. Ele também pode melhorar muito 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 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 de apresentação; e o modelo, que encapsula a lógica de negócios e os dados do aplicativo.