O padrão Model-View-ViewModelThe Model-View-ViewModel Pattern

A experiência do desenvolvedor do Xamarin. Forms normalmente envolve a criação de uma interface do usuário em XAML e, em seguida, a adição do code-behind que opera na interface do usuário.The Xamarin.Forms developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. À medida que os aplicativos são modificados e aumentam em tamanho e escopo, podem surgir problemas complexos de manutenção.As apps are modified, and grow in size and scope, complex maintenance issues can arise. Esses problemas incluem o forte acoplamento 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 teste de unidade desse código.These issues include the tight coupling between the UI controls and the business logic, which increases the cost of making UI modifications, and the difficulty of unit testing such code.

O padrão MVVM (Model-View-ViewModel) ajuda a separar corretamente a lógica de negócios e de apresentação de um aplicativo da interface do usuário.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Manter uma separação clara entre a lógica do aplicativo e a interface do usuário ajuda a resolver inúmeros problemas de desenvolvimento e pode facilitar o teste, a manutenção e a evolução de um aplicativo.Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. Ele também pode melhorar consideravelmente 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.It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

O padrão MVVMThe MVVM Pattern

Há três componentes principais no padrão MVVM: o modelo, a exibição e o modelo de exibição.There are three core components in the MVVM pattern: the model, the view, and the view model. Cada um serve para uma finalidade distinta.Each serves a distinct purpose. A Figura 2-1 mostra as relações entre os três componentes.Figure 2-1 shows the relationships between the three components.

Figura 2-1: O padrão MVVMFigure 2-1: The MVVM pattern

Além de compreender as responsabilidades de cada componente, também é importante entender como eles interagem entre si.In addition to understanding the responsibilities of each component, it's also important to understand how they interact with each other. Em um 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 reconhece o modelo de exibição e o modelo de exibição não reconhece a exibição.At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. Portanto, o modelo de exibição isola a exibição do modelo e permite que o modelo evolua independentemente da exibição.Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.

Os benefícios de usar o padrão MVVM são os seguintes:The benefits of using the MVVM pattern are as follows:

  • Se houver uma implementação de modelo existente que encapsula a lógica de negócios existente, pode ser difícil ou arriscar alterá-la.If there's an existing model implementation that encapsulates existing business logic, it can be difficult or risky to change it. 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.In this scenario, the view model acts as an adapter for the model classes and enables you to avoid making any major changes to the model code.
  • Os desenvolvedores podem criar testes de unidade para o modelo de exibição e o modelo, sem usar a exibição.Developers can create unit tests for the view model and the model, without using the view. Os testes de unidade do modelo de exibição podem exercer exatamente a mesma funcionalidade usada pelo modo de exibição.The unit tests for the view model can exercise exactly the same functionality as used by the view.
  • A interface do usuário do aplicativo pode ser reformulada sem tocar no código, desde que a exibição seja implementada inteiramente em XAML.The app UI can be redesigned without touching the code, provided that the view is implemented entirely in XAML. Portanto, uma nova versão da exibição deve funcionar com o modelo de exibição existente.Therefore, a new version of the view should work with the existing view model.
  • Designers e desenvolvedores podem trabalhar de forma independente e simultânea em seus componentes durante o processo de desenvolvimento.Designers and developers can work independently and concurrently on their components during the development process. Os designers podem se concentrar na exibição, enquanto os desenvolvedores podem trabalhar no modelo de exibição e nos componentes de modelo.Designers can focus on the view, while developers can work on the view model and model components.

A chave para usar o MVVM efetivamente está na compreensão de como fatorar o código do aplicativo nas classes corretas e no entendimento de como as classes interagem.The key to using MVVM effectively lies in understanding how to factor app code into the correct classes, and in understanding how the classes interact. As seções a seguir discutem as responsabilidades de cada uma das classes no padrão MVVM.The following sections discuss the responsibilities of each of the classes in the MVVM pattern.

ExibirView

A exibição é responsável por definir a estrutura, o layout e a aparência do que o usuário vê na tela.The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. O ideal é que cada exibição seja definida em XAML, com um code-behind limitado que não contenha lógica de negócios.Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. No entanto, em alguns casos, o code-behind pode conter lógica de interface do usuário que implementa o comportamento visual que é difícil de expressar em XAML, como animações.However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.

Em um aplicativo Xamarin. Forms, uma exibição é normalmente uma Pageclasse derivada ou ContentViewderivada.In a Xamarin.Forms application, a view is typically a Page-derived or ContentView-derived class. 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.However, views can also be represented by a data template, which specifies the UI elements to be used to visually represent an object when it's displayed. Um modelo de dados como uma exibição não tem nenhum code-behind e é projetado para ser associado a um tipo de modelo de exibição específico.A data template as a view does not have any code-behind, and is designed to bind to a specific view model type.

Dica

Evite habilitar e desabilitar elementos da interface do usuário no code-behind.Avoid enabling and disabling UI elements in the code-behind. Verifique se os modelos de exibição são responsáveis por definir alterações de estado lógico que afetem alguns aspectos 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.Ensure that view models are responsible for defining logical state changes that affect some aspects of the view's display, such as whether a command is available, or an indication that an operation is pending. Portanto, habilite e desabilite elementos da interface do usuário ligando para propriedades do modelo de exibição, em vez de habilitá-los e desabilitá-los no code-behind.Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind.

Há várias opções para executar o código no modelo de exibição em resposta a interações na exibição, como um clique de botão ou seleção de item.There are several options for executing code on the view model in response to interactions on the view, such as a button click or item selection. Se um controle der suporte a comandos, a Command Propriedade do controle poderá ser vinculada a ICommand dados a uma propriedade no modelo de exibição.If a control supports commands, the control's Command property can be data-bound to an ICommand property on the view model. Quando o comando do controle for invocado, o código no modelo de exibição será executado.When the control's command is invoked, the code in the view model will be executed. 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.In addition to commands, behaviors can be attached to an object in the view and can listen for either a command to be invoked or event to be raised. Em resposta, o comportamento pode invocar um ICommand no modelo de exibição ou um método no modelo de exibição.In response, the behavior can then invoke an ICommand on the view model or a method on the view model.

ViewModelViewModel

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.The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. As propriedades e os comandos que o modelo de exibição fornece definem a funcionalidade a ser oferecida pela interface do usuário, mas a exibição determina como essa funcionalidade deve ser exibida.The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.

Dica

Mantenha a interface do usuário responsiva com operações assíncronas.Keep the UI responsive with asynchronous operations. Os aplicativos móveis devem manter o thread da interface do usuário desbloqueado para melhorar a percepção do usuário do desempenho.Mobile apps should keep the UI thread unblocked to improve the user's perception of performance. 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 propriedade de forma assíncrona.Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes.

O modelo de exibição também é responsável por coordenar as interações de exibição com todas as classes de modelo necessárias.The view model is also responsible for coordinating the view's interactions with any model classes that are required. Normalmente, há uma relação um-para-muitos entre o modelo de exibição e as classes de modelo.There's typically a one-to-many relationship between the view model and the model classes. 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.The view model might choose to expose model classes directly to the view so that controls in the view can data bind directly to them. Nesse caso, as classes de modelo precisarão ser projetadas para dar suporte a vinculação de dados e eventos de notificação de alteração.In this case, the model classes will need to be designed to support data binding and change notification events.

Cada modelo de exibição fornece dados de um modelo em um formulário que a exibição pode facilmente consumir.Each view model provides data from a model in a form that the view can easily consume. Para fazer isso, o modelo de exibição às vezes executa a conversão de dados.To accomplish this, the view model sometimes performs data conversion. 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.Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. Por exemplo, o modelo de exibição pode combinar os valores de duas propriedades para facilitar a exibição pela exibição.For example, the view model might combine the values of two properties to make it easier for display by the view.

Dica

Centralizar conversões de dados em uma camada de conversão.Centralize data conversions in a conversion layer. 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.It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. Isso pode ser necessário, por exemplo, quando os dados exigem formatação especial que o modelo de exibição não fornece.This can be necessary, for example, when data requires special formatting that the view model doesn't provide.

Para que o modelo de exibição participe da ligação de dados bidirecional com a exibição, suas propriedades devem gerar o PropertyChanged evento.In order for the view model to participate in two-way data binding with the view, its properties must raise the PropertyChanged event. Os modelos de exibição atendem a esse INotifyPropertyChanged requisito implementando a interface PropertyChanged e gerando o evento quando uma propriedade é alterada.View models satisfy this requirement by implementing the INotifyPropertyChanged interface, and raising the PropertyChanged event when a property is changed.

Para coleções, é fornecido o modo ObservableCollection<T> de exibição amigável.For collections, the view-friendly ObservableCollection<T> is provided. Essa coleção implementa a notificação de alteração de coleção, aliviando o desenvolvedor de ter INotifyCollectionChanged que implementar a interface em coleções.This collection implements collection changed notification, relieving the developer from having to implement the INotifyCollectionChanged interface on collections.

ModeloModel

Classes de modelo são classes não visuais que encapsulam os dados do aplicativo.Model classes are non-visual classes that encapsulate the app's data. Portanto, o modelo pode ser considerado como representando o modelo de domínio do aplicativo, que geralmente inclui um modelo de dados juntamente com a lógica de validação e negócios.Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. Exemplos de objetos de modelo incluem DTOs (objetos de transferência de dados), POCOs (objetos de CLR antigos) e objetos de proxy e entidade gerados.Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects.

As classes de modelo normalmente são usadas em conjunto com serviços ou repositórios que encapsulam o acesso a dados e o cache.Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching.

Conectando modelos de exibição a exibiçõesConnecting View Models to Views

Os modelos de exibição podem ser conectados a exibições usando os recursos de vinculação de dados do Xamarin. Forms.View models can be connected to views by using the data-binding capabilities of Xamarin.Forms. Há muitas abordagens que podem ser usadas para construir exibições e modelos de exibição e associá-las em tempo de execução.There are many approaches that can be used to construct views and view models and associate them at runtime. Essas abordagens se enquadram em duas categorias, conhecidas como exibição da primeira composição e a primeira composição do modelo de exibição.These approaches fall into two categories, known as view first composition, and view model first composition. Escolher entre exibir primeira composição e exibir a primeira composição do modelo é um problema de preferência e complexidade.Choosing between view first composition and view model first composition is an issue of preference and complexity. No entanto, todas as abordagens compartilham o mesmo objetivo, que é para a exibição ter um modelo de exibição atribuído à sua Propriedade BindingContext.However, all approaches share the same aim, which is for the view to have a view model assigned to its BindingContext property.

Com a primeira composição da exibição, o aplicativo é conceitualmente composto de exibições que se conectam aos modelos de exibição dos quais dependem.With view first composition the app is conceptually composed of views that connect to the view models they depend on. O principal benefício dessa abordagem é que ela facilita a construção de aplicativos de teste de unidade menos rígidos, pois os modelos de exibição não têm nenhuma dependência das próprias exibições.The primary benefit of this approach is that it makes it easy to construct loosely coupled, unit testable apps because the view models have no dependence on the views themselves. Também é fácil entender a estrutura do aplicativo seguindo sua estrutura visual, em vez de ter que controlar a execução do código para entender como as classes são criadas e associadas.It's also easy to understand the structure of the app by following its visual structure, rather than having to track code execution to understand how classes are created and associated. Além disso, a primeira construção se alinha com o sistema de navegação Xamarin. Forms que é responsável pela construção de páginas quando ocorre a navegação, o que faz com que um modelo de exibição seja formado pela primeira vez complexo e alinhado com a plataforma.In addition, view first construction aligns with the Xamarin.Forms navigation system that's responsible for constructing pages when navigation occurs, which makes a view model first composition complex and misaligned with the platform.

Com a primeira composição do modelo de exibição, o aplicativo é conceitualmente composto por modelos de exibição, sendo que um serviço é responsável por localizar a exibição de um modelo de exibição.With view model first composition the app is conceptually composed of view models, with a service being responsible for locating the view for a view model. A primeira composição do modelo de exibição se sente mais natural para alguns desenvolvedores, já que a criação da exibição pode ser descartada, permitindo que eles se concentrem na estrutura lógica fora da interface do usuário do aplicativo.View model first composition feels more natural to some developers, since the view creation can be abstracted away, allowing them to focus on the logical non-UI structure of the app. Além disso, ele permite que os modelos de exibição sejam criados por outros modelos de exibição.In addition, it allows view models to be created by other view models. 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.However, this approach is often complex and it can become difficult to understand how the various parts of the app are created and associated.

Dica

Mantenha os modelos de exibição e exibições independentes.Keep view models and views independent. 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.The binding of views to a property in a data source should be the view's principal dependency on its corresponding view model. Especificamente, não referencie tipos de exibição, Button como e ListView, de modelos de exibição.Specifically, don't reference view types, such as Button and ListView, from view models. 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.By following the principles outlined here, view models can be tested in isolation, therefore reducing the likelihood of software defects by limiting scope.

As seções a seguir discutem as principais abordagens para conectar modelos de exibição a exibições.The following sections discuss the main approaches to connecting view models to views.

Criando um modelo de exibição declarativamenteCreating a View Model Declaratively

A abordagem mais simples é a exibição para instanciar declarativamente seu modelo de exibição correspondente em XAML.The simplest approach is for the view to declaratively instantiate its corresponding view model in XAML. Quando a exibição é construída, o objeto de modelo de exibição correspondente também será construído.When the view is constructed, the corresponding view model object will also be constructed. Essa abordagem é demonstrada no exemplo de código a seguir:This approach is demonstrated in the following code example:

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

Quando o ContentPage é criado, uma instância LoginViewModel do é criada automaticamente e BindingContextdefinida como a exibição.When the ContentPage is created, an instance of the LoginViewModel is automatically constructed and set as the view's BindingContext.

Essa construção declarativa e a atribuição do modelo de exibição pela exibição tem a vantagem de que é simples, mas tem a desvantagem de que ele requer um construtor padrão (sem parâmetros) no modelo de exibição.This declarative construction and assignment of the view model by the view has the advantage that it's simple, but has the disadvantage that it requires a default (parameter-less) constructor in the view model.

Criando um modelo de exibição programaticamenteCreating a View Model Programmatically

Um modo de exibição pode ter código no arquivo code-behind que resulta na atribuição do modelo de exibição à BindingContext sua propriedade.A view can have code in the code-behind file that results in the view model being assigned to its BindingContext property. Isso geralmente é feito no construtor da exibição, conforme mostrado no exemplo de código a seguir:This is often accomplished in the view's constructor, as shown in the following code example:

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 que é simples.The programmatic construction and assignment of the view model within the view's code-behind has the advantage that it's simple. No entanto, a principal desvantagem dessa abordagem é que a exibição precisa fornecer o modelo de exibição com quaisquer dependências necessárias.However, the main disadvantage of this approach is that the view needs to provide the view model with any required dependencies. 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.Using a dependency injection container can help to maintain loose coupling between the view and view model. Para obter mais informações, consulte injeção de dependência.For more information, see Dependency Injection.

Criando uma exibição definida como um modelo de dadosCreating a View Defined as a Data Template

Uma exibição pode ser definida como um modelo de dados e associada a um tipo de modelo de exibição.A view can be defined as a data template and associated with a view model type. Os modelos de dados podem ser definidos como recursos ou podem ser definidos embutidos dentro do controle que exibirá o modelo de exibição.Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. O conteúdo do controle é a instância do modelo de exibição e o modelo de dados é usado para representá-lo visualmente.The content of the control is the view model instance, and the data template is used to visually represent it. Essa técnica é um exemplo de uma situação na qual o modelo de exibição é instanciado primeiro, seguido pela criação da exibição.This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.

Criando automaticamente um modelo de exibição com um localizador de modelo de exibiçãoAutomatically Creating a View Model with a View Model Locator

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.A view model locator is a custom class that manages the instantiation of view models and their association to views. No aplicativo móvel eShopOnContainers, a ViewModelLocator classe tem uma propriedade anexada, AutoWireViewModel, que é usada para associar modelos de exibição com exibições.In the eShopOnContainers mobile app, the ViewModelLocator class has an attached property, AutoWireViewModel, that's used to associate view models with views. No XAML da exibição, essa propriedade anexada é definida como true para indicar que o modelo de exibição deve ser conectado automaticamente à exibição, conforme mostrado no exemplo de código a seguir:In the view's XAML, this attached property is set to true to indicate that the view model should be automatically connected to the view, as shown in the following code example:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

A AutoWireViewModel propriedade é uma propriedade vinculável que é inicializada como false e quando seu valor altera OnAutoWireViewModelChanged o manipulador de eventos é chamado.The AutoWireViewModel property is a bindable property that's initialized to false, and when its value changes the OnAutoWireViewModelChanged event handler is called. Esse método resolve o modelo de exibição para a exibição.This method resolves the view model for the view. O exemplo de código a seguir mostra como isso é obtido:The following code example shows how this is achieved:

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.The OnAutoWireViewModelChanged method attempts to resolve the view model using a convention-based approach. Essa convenção pressupõe que:This convention assumes that:

  • Os modelos de exibição estão no mesmo assembly que os tipos de exibição.View models are in the same assembly as view types.
  • Os modos de exibição estão em um. Exibe o namespace filho.Views are in a .Views child namespace.
  • Os modelos de exibição estão em um. Namespace filho de ViewModels.View models are in a .ViewModels child namespace.
  • Os nomes de modelo de exibição correspondem aos nomes de exibição e terminam com "ViewModel".View model names correspond with view names and end with "ViewModel".

Por fim, OnAutoWireViewModelChanged o método define BindingContext o do tipo de exibição como o tipo de modelo de exibição resolvido.Finally, the OnAutoWireViewModelChanged method sets the BindingContext of the view type to the resolved view model type. Para obter mais informações sobre como resolver o tipo de modelo de exibição, consulte resolução.For more information about resolving the view model type, see Resolution.

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.This approach has the advantage that an app has a single class that is responsible for the instantiation of view models and their connection to views.

Dica

Use um localizador de modelo de exibição para facilitar a substituição.Use a view model locator for ease of substitution. 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 teste de unidade ou dados de tempo de design.A view model locator can also be used as a point of substitution for alternate implementations of dependencies, such as for unit testing or design time data.

Atualizando exibições em resposta a alterações no modelo de exibição ou modelo subjacenteUpdating Views in Response to Changes in the Underlying View Model or Model

Todas as classes modelo de exibição e modelo que são acessíveis a uma exibição devem INotifyPropertyChanged implementar a interface.All view model and model classes that are accessible to a view should implement the INotifyPropertyChanged interface. Implementar essa interface em um modelo de exibição ou classe de modelo permite que a classe forneça notificações de alteração a quaisquer controles vinculados a dados na exibição quando o valor da propriedade subjacente é alterado.Implementing this interface in a view model or model class allows the class to provide change notifications to any data-bound controls in the view when the underlying property value changes.

Os aplicativos devem ser arquitetados para o uso correto da notificação de alteração de propriedade, atendendo aos seguintes requisitos:Apps should be architected for the correct use of property change notification, by meeting the following requirements:

  • Sempre gerando PropertyChanged um evento se o valor de uma propriedade pública for alterado.Always raising a PropertyChanged event if a public property's value changes. Não presuma que a geração do PropertyChanged evento pode ser ignorada devido ao conhecimento de como ocorre a associação XAML.Do not assume that raising the PropertyChanged event can be ignored because of knowledge of how XAML binding occurs.
  • Sempre gerando PropertyChanged um evento para todas as propriedades calculadas cujos valores são usados por outras propriedades no modelo de exibição ou modelo.Always raising a PropertyChanged event for any calculated properties whose values are used by other properties in the view model or model.
  • Sempre gerando PropertyChanged o evento no final do método que faz uma alteração de propriedade ou quando o objeto é conhecido como em um estado seguro.Always raising the PropertyChanged event at the end of the method that makes a property change, or when the object is known to be in a safe state. Gerar o evento interrompe a operação invocando os manipuladores do evento de forma síncrona.Raising the event interrupts the operation by invoking the event's handlers synchronously. Se isso ocorrer no meio de uma operação, ele poderá expor o objeto às funções de retorno de chamada quando ele estiver em um estado inseguro e parcialmente atualizado.If this happens in the middle of an operation, it might expose the object to callback functions when it is in an unsafe, partially updated state. Além disso, é possível que as alterações em cascata sejam disparadas PropertyChanged por eventos.In addition, it's possible for cascading changes to be triggered by PropertyChanged events. 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.Cascading changes generally require updates to be complete before the cascading change is safe to execute.
  • Nunca gerar um PropertyChanged evento se a propriedade não for alterada.Never raising a PropertyChanged event if the property does not change. Isso significa que você deve comparar os valores antigos e novos antes de gerar PropertyChanged o evento.This means that you must compare the old and new values before raising the PropertyChanged event.
  • Nunca gerará PropertyChanged o evento durante um construtor do modelo de exibição se você estiver inicializando uma propriedade.Never raising the PropertyChanged event during a view model's constructor if you are initializing a property. Os controles vinculados a dados na exibição não terão assinatura para receber notificações de alteração neste momento.Data-bound controls in the view will not have subscribed to receive change notifications at this point.
  • Nunca gera mais de um PropertyChanged evento com o mesmo argumento de nome de propriedade em uma única invocação síncrona de um método público de uma classe.Never raising more than one PropertyChanged event with the same property name argument within a single synchronous invocation of a public method of a class. Por exemplo, considerando uma NumberOfItems propriedade cujo armazenamento de backup é o _numberOfItems campo, _numberOfItems se um método incrementar 50 vezes durante a execução de um loop, ele só NumberOfItems deverá gerar uma notificação de alteração de propriedade na propriedade uma vez, Depois que todo o trabalho for concluído.For example, given a NumberOfItems property whose backing store is the _numberOfItems field, if a method increments _numberOfItems fifty times during the execution of a loop, it should only raise property change notification on the NumberOfItems property once, after all the work is complete. Para métodos assíncronos, gere PropertyChanged o evento para um determinado nome de propriedade em cada segmento síncrono de uma cadeia de continuação assíncrona.For asynchronous methods, raise the PropertyChanged event for a given property name in each synchronous segment of an asynchronous continuation chain.

O aplicativo móvel eShopOnContainers usa a ExtendedBindableObject classe para fornecer notificações de alteração, que é mostrada no exemplo de código a seguir:The eShopOnContainers mobile app uses the ExtendedBindableObject class to provide change notifications, which is shown in the following code example:

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 INotifyPropertyChanged a interface e fornece um OnPropertyChanged método.Xamarin.Form's BindableObject class implements the INotifyPropertyChanged interface, and provides an OnPropertyChanged method. A ExtendedBindableObject classe fornece o RaisePropertyChanged método para invocar a notificação de alteração de propriedade e, ao fazer isso, usa BindableObject a funcionalidade fornecida pela classe.The ExtendedBindableObject class provides the RaisePropertyChanged method to invoke property change notification, and in doing so uses the functionality provided by the BindableObject class.

Cada classe de modelo de exibição no aplicativo móvel eShopOnContainers deriva da ViewModelBase classe, que, por sua vez, deriva ExtendedBindableObject da classe.Each view model class in the eShopOnContainers mobile app derives from the ViewModelBase class, which in turn derives from the ExtendedBindableObject class. Portanto, cada classe de modelo de exibição RaisePropertyChanged usa o método ExtendedBindableObject na classe para fornecer notificação de alteração de propriedade.Therefore, each view model class uses the RaisePropertyChanged method in the ExtendedBindableObject class to provide property change notification. 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:The following code example shows how the eShopOnContainers mobile app invokes property change notification by using a lambda expression:

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

Observe que usar uma expressão lambda dessa maneira envolve um pequeno custo de desempenho, pois a expressão lambda deve ser avaliada para cada chamada.Note that using a lambda expression in this way involves a small performance cost because the lambda expression has to be evaluated for each call. 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.Although the performance cost is small and would not normally impact an app, the costs can accrue when there are many change notifications. No entanto, o benefício dessa abordagem é que ela fornece segurança de tipo em tempo de compilação e suporte de refatoração ao renomear as propriedades.However, the benefit of this approach is that it provides compile-time type safety and refactoring support when renaming properties.

Interação da interface do usuário usando comandos e comportamentosUI Interaction using Commands and Behaviors

Em aplicativos móveis, as ações geralmente são invocadas em resposta a uma ação do usuário, como um clique de botão, que pode ser implementado pela criação de um manipulador de eventos no arquivo code-behind.In mobile apps, actions are typically invoked in response to a user action, such as a button click, that can be implemented by creating an event handler in the code-behind file. No entanto, no padrão MVVM, a responsabilidade de implementar a ação está no modelo de exibição, e colocar o código no code-behind deve ser evitado.However, in the MVVM pattern, the responsibility for implementing the action lies with the view model, and placing code in the code-behind should be avoided.

Os comandos fornecem uma maneira conveniente de representar as ações que podem ser associadas a controles na interface do usuário.Commands provide a convenient way to represent actions that can be bound to controls in the UI. Eles encapsulam o código que implementa a ação e ajudam a mantê-lo dissociado de sua representação visual na exibição.They encapsulate the code that implements the action, and help to keep it decoupled from its visual representation in the view. O Xamarin. Forms inclui controles que podem ser conectados declarativamente a um comando, e esses controles invocarão o comando quando o usuário interage com o controle.Xamarin.Forms includes controls that can be declaratively connected to a command, and these controls will invoke the command when the user interacts with the control.

Os comportamentos também permitem que os controles sejam conectados declarativamente a um comando.Behaviors also allow controls to be declaratively connected to a command. No entanto, os comportamentos podem ser usados para invocar uma ação associada a um intervalo de eventos gerados por um controle.However, behaviors can be used to invoke an action that's associated with a range of events raised by a control. Portanto, os comportamentos abordam muitos dos mesmos cenários que os controles habilitados para comandos, fornecendo, ao mesmo tempo, um grau maior de flexibilidade e controle.Therefore, behaviors address many of the same scenarios as command-enabled controls, while providing a greater degree of flexibility and control. Além disso, os comportamentos também podem ser usados para associar objetos de comando ou métodos a controles que não foram projetados especificamente para interagir com comandos.In addition, behaviors can also be used to associate command objects or methods with controls that were not specifically designed to interact with commands.

Implementando comandosImplementing Commands

Os modelos de exibição normalmente expõem propriedades de comando, para associação da exibição, que são instâncias de ICommand objeto que implementam a interface.View models typically expose command properties, for binding from the view, that are object instances that implement the ICommand interface. Vários controles Xamarin. Forms fornecem uma Command Propriedade, que pode ser associada a um ICommand objeto fornecido pelo modelo de exibição.A number of Xamarin.Forms controls provide a Command property, which can be data bound to an ICommand object provided by the view model. A ICommand interface define um Execute método, que encapsula a própria operação, 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.The ICommand interface defines an Execute method, which encapsulates the operation itself, a CanExecute method, which indicates whether the command can be invoked, and a CanExecuteChanged event that occurs when changes occur that affect whether the command should execute. As Command classes Command<T> e, fornecidas pelo Xamarin. Forms, implementam ICommand a interface, T em que é o tipo dos argumentos Execute para CanExecutee.The Command and Command<T> classes, provided by Xamarin.Forms, implement the ICommand interface, where T is the type of the arguments to Execute and 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.Within a view model, there should be an object of type Command or Command<T> for each public property in the view model of type ICommand. O Command Construtor Command<T> ou requer um Action objeto de retorno de chamada que é ICommand.Execute chamado quando o método é invocado.The Command or Command<T> constructor requires an Action callback object that's called when the ICommand.Execute method is invoked. O CanExecute método é um parâmetro de Construtor opcional e é um Func que retorna um bool.The CanExecute method is an optional constructor parameter, and is a Func that returns a bool.

O código a seguir mostra como Command uma instância, que representa um comando de registro, é construída especificando um delegado para Register o método de modelo de exibição:The following code shows how a Command instance, which represents a register command, is constructed by specifying a delegate to the Register view model method:

public ICommand RegisterCommand => new Command(Register);

O comando é exposto à exibição por meio de uma propriedade que retorna uma referência a ICommandum.The command is exposed to the view through a property that returns a reference to an ICommand. Quando o Execute método é chamado Command no objeto, ele simplesmente encaminha a chamada para o método no modelo de exibição por meio do delegado Command que foi especificado no construtor.When the Execute method is called on the Command object, it simply forwards the call to the method in the view model via the delegate that was specified in the Command constructor.

Um método assíncrono pode ser invocado por um comando usando as async palavras await -chave e ao especificar o delegado do Execute comando.An asynchronous method can be invoked by a command by using the async and await keywords when specifying the command's Execute delegate. Isso indica que o retorno de chamada Task é a e deve ser aguardado.This indicates that the callback is a Task and should be awaited. Por exemplo, o código a seguir mostra como Command uma instância, que representa um comando de entrada, é construída especificando um delegado para o método SignInAsync de modelo de exibição:For example, the following code shows how a Command instance, which represents a sign-in command, is constructed by specifying a delegate to the SignInAsync view model method:

public ICommand SignInCommand => new Command(async () => await SignInAsync());

Os parâmetros podem ser passados para Execute as CanExecute ações e usando a Command<T> classe para instanciar o comando.Parameters can be passed to the Execute and CanExecute actions by using the Command<T> class to instantiate the command. Por exemplo, o código a seguir mostra como Command<T> uma instância é usada para indicar que NavigateAsync o método exigirá um argumento do stringtipo:For example, the following code shows how a Command<T> instance is used to indicate that the NavigateAsync method will require an argument of type string:

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

Nas classes Command<T> CanExecute e, o delegado para o método em cada Construtor é opcional. CommandIn both the Command and Command<T> classes, the delegate to the CanExecute method in each constructor is optional. Se um delegado não for especificado, Command o será true retornado CanExecutepara.If a delegate isn't specified, the Command will return true for CanExecute. No entanto, o modelo de exibição pode indicar uma alteração no CanExecute status do comando chamando ChangeCanExecute o método no Command objeto.However, the view model can indicate a change in the command's CanExecute status by calling the ChangeCanExecute method on the Command object. Isso faz com CanExecuteChanged que o evento seja gerado.This causes the CanExecuteChanged event to be raised. Todos os controles na interface do usuário que estão vinculados ao comando atualizarão seu status habilitado para refletir a disponibilidade do comando associado a dados.Any controls in the UI that are bound to the command will then update their enabled status to reflect the availability of the data-bound command.

Invocando comandos de uma exibiçãoInvoking Commands from a View

O exemplo de código a seguir mostra Grid como um LoginView no é associado ao RegisterCommand na LoginViewModel classe usando uma TapGestureRecognizer instância:The following code example shows how a Grid in the LoginView binds to the RegisterCommand in the LoginViewModel class by using a TapGestureRecognizer instance:

<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 CommandParameter a propriedade.A command parameter can also be optionally defined using the CommandParameter property. O tipo do argumento esperado é especificado nos métodos de Execute destino CanExecute e.The type of the expected argument is specified in the Execute and CanExecute target methods. O TapGestureRecognizer irá invocar automaticamente o comando de destino quando o usuário interage com o controle anexado.The TapGestureRecognizer will automatically invoke the target command when the user interacts with the attached control. O parâmetro de comando, se fornecido, será passado como o argumento para o delegado do Execute comando.The command parameter, if provided, will be passed as the argument to the command's Execute delegate.

Implementando comportamentosImplementing Behaviors

Os comportamentos permitem que a funcionalidade seja adicionada aos controles da interface do usuário sem a necessidade de subclasse deles.Behaviors allow functionality to be added to UI controls without having to subclass them. Em vez disso, a funcionalidade é implementada em uma classe de comportamento e anexada ao controle como se fizesse parte do próprio controle.Instead, the functionality is implemented in a behavior class and attached to the control as if it was part of the control itself. Os comportamentos permitem que você implemente 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 maneira concisa ao controle e empacotado para reutilização em mais de uma exibição ou aplicativo.Behaviors enable you to implement code that you would normally have to write as code-behind, because it directly interacts with the API of the control, in such a way that it can be concisely attached to the control, and packaged for reuse across more than one view or app. No contexto do MVVM, os comportamentos são uma abordagem útil para conectar controles a comandos.In the context of MVVM, behaviors are a useful approach for connecting controls to commands.

Um comportamento que é anexado a um controle por meio de propriedades anexadas é conhecido como um comportamento anexado.A behavior that's attached to a control through attached properties is known as an attached behavior. 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.The behavior can then use the exposed API of the element to which it is attached to add functionality to that control, or other controls, in the visual tree of the view. O aplicativo móvel eShopOnContainers contém a LineColorBehavior classe, que é um comportamento anexado.The eShopOnContainers mobile app contains the LineColorBehavior class, which is an attached behavior. Para obter mais informações sobre esse comportamento, consulte exibindo erros de validação.For more information about this behavior, see Displaying Validation Errors.

Um comportamento do Xamarin. Forms é uma classe derivada da Behavior classe ou Behavior<T> , em que T é o tipo do controle ao qual o comportamento deve ser aplicado.A Xamarin.Forms behavior is a class that derives from the Behavior or Behavior<T> class, where T is the type of the control to which the behavior should apply. Essas classes fornecem OnAttachedTo métodos OnDetachingFrom e, que devem ser substituídos para fornecer lógica que será executada quando o comportamento for anexado e desanexado dos controles.These classes provide OnAttachedTo and OnDetachingFrom methods, which should be overridden to provide logic that will be executed when the behavior is attached to and detached from controls.

No aplicativo móvel eShopOnContainers, a BindableBehavior<T> classe deriva Behavior<T> da classe.In the eShopOnContainers mobile app, the BindableBehavior<T> class derives from the Behavior<T> class. A finalidade da BindableBehavior<T> classe é fornecer uma classe base para comportamentos do Xamarin. Forms que exijam que BindingContext o comportamento seja definido para o controle anexado.The purpose of the BindableBehavior<T> class is to provide a base class for Xamarin.Forms behaviors that require the BindingContext of the behavior to be set to the attached control.

A BindableBehavior<T> classe fornece um método OnAttachedTo substituível que define BindingContext o do comportamento e um método substituível OnDetachingFrom que limpa o BindingContext.The BindableBehavior<T> class provides an overridable OnAttachedTo method that sets the BindingContext of the behavior, and an overridable OnDetachingFrom method that cleans up the BindingContext. Além disso, a classe armazena uma referência ao controle anexado na propriedade AssociatedObject.In addition, the class stores a reference to the attached control in the AssociatedObject property.

O aplicativo móvel eShopOnContainers inclui uma EventToCommandBehavior classe, que executa um comando em resposta a um evento que ocorre.The eShopOnContainers mobile app includes an EventToCommandBehavior class, which executes a command in response to an event occurring. Essa classe deriva da BindableBehavior<T> classe para que o comportamento possa se associar a e executar um ICommand especificado por uma Command Propriedade quando o comportamento for consumido.This class derives from the BindableBehavior<T> class so that the behavior can bind to and execute an ICommand specified by a Command property when the behavior is consumed. O exemplo de código a seguir mostra a classe EventToCommandBehavior:The following code example shows the EventToCommandBehavior class:

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 OnAttachedTo métodos OnDetachingFrom e são usados para registrar e cancelar o registro de um manipulador de eventos para EventName o evento definido na propriedade.The OnAttachedTo and OnDetachingFrom methods are used to register and deregister an event handler for the event defined in the EventName property. Em seguida, quando o evento é acionado, o OnFired método é invocado, que executa o comando.Then, when the event fires, the OnFired method is invoked, which executes the command.

A vantagem de usar o 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.The advantage of using the EventToCommandBehavior to execute a command when an event fires, is that commands can be associated with controls that weren't designed to interact with commands. Além disso, isso move o código de manipulação de eventos para exibir modelos, em que ele pode ser testado por unidade.In addition, this moves event-handling code to view models, where it can be unit tested.

Invocando comportamentos de uma exibiçãoInvoking Behaviors from a View

O EventToCommandBehavior é particularmente útil para anexar um comando a um controle que não dá suporte a comandos.The EventToCommandBehavior is particularly useful for attaching a command to a control that doesn't support commands. Por exemplo, o ProfileView usa o EventToCommandBehavior para executar o OrderDetailCommand quando o ItemTapped evento é acionado ListView no que lista os pedidos do usuário, conforme mostrado no código a seguir:For example, the ProfileView uses the EventToCommandBehavior to execute the OrderDetailCommand when the ItemTapped event fires on the ListView that lists the user's orders, as shown in the following code:

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

Em tempo de execução EventToCommandBehavior , o responderá à interação ListViewcom o.At runtime, the EventToCommandBehavior will respond to interaction with the ListView. Quando um item for selecionado ListViewno, o evento será acionado, o ItemTapped que executará OrderDetailCommand o no ProfileViewModel.When an item is selected in the ListView, the ItemTapped event will fire, which will execute the OrderDetailCommand in the ProfileViewModel. Por padrão, os argumentos de evento para o evento são passados para o comando.By default, the event arguments for the event are passed to the command. Esses dados são convertidos à medida que são passados entre a origem e o destino pelo conversor EventArgsConverter especificado na propriedade, que Item retorna o ListView do ItemTappedEventArgsdo.This data is converted as it's passed between source and target by the converter specified in the EventArgsConverter property, which returns the Item of the ListView from the ItemTappedEventArgs. Portanto, quando o OrderDetailCommand é executado, o selecionado Order é passado como um parâmetro para a ação registrada.Therefore, when the OrderDetailCommand is executed, the selected Order is passed as a parameter to the registered Action.

Para obter mais informações sobre comportamentos, consulte comportamentos.For more information about behaviors, see Behaviors.

ResumoSummary

O padrão MVVM (Model-View-ViewModel) ajuda a separar corretamente a lógica de negócios e de apresentação de um aplicativo da interface do usuário.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Manter uma separação clara entre a lógica do aplicativo e a interface do usuário ajuda a resolver inúmeros problemas de desenvolvimento e pode facilitar o teste, a manutenção e a evolução de um aplicativo.Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. Ele também pode melhorar consideravelmente 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.It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

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 de apresentação e o estado; e o modelo, que encapsula a lógica de negócios e os dados do aplicativo.Using the MVVM pattern, the UI of the app and the underlying presentation and business logic is separated into three separate classes: the view, which encapsulates the UI and UI logic; the view model, which encapsulates presentation logic and state; and the model, which encapsulates the app's business logic and data.