MVVM - Guia Rápido

A maneira bem ordenada e talvez a mais reutilizável de organizar seu código é usar o padrão 'MVVM'. oModel, View, ViewModel (MVVM pattern) tem tudo a ver com orientar você sobre como organizar e estruturar seu código para escrever aplicativos sustentáveis, testáveis ​​e extensíveis.

Model - Ele simplesmente contém os dados e não tem nada a ver com qualquer lógica de negócios.

ViewModel - Ele atua como o link / conexão entre o Modelo e a Visualização e torna as coisas bonitas.

View - Ele simplesmente mantém os dados formatados e essencialmente delega tudo ao Modelo.

Apresentação Separada

Para evitar os problemas causados ​​ao colocar a lógica do aplicativo em code-behind ou XAML, é melhor usar uma técnica conhecida como apresentação separada. Estamos tentando evitar isso, onde teremos XAML e code-behind com o mínimo necessário para trabalhar diretamente com objetos de interface do usuário. As classes de interface do usuário também contêm código para comportamentos de interação complexos, lógica de aplicativo e tudo o mais, conforme mostrado na figura a seguir no lado esquerdo.

  • Com a apresentação separada, a classe de interface do usuário é muito mais simples. Ele tem o XAML, é claro, mas o código por trás faz tão pouco quanto é prático.

  • A lógica do aplicativo pertence a uma classe separada, que geralmente é chamada de modelo.

  • No entanto, esta não é toda a história. Se você parar aqui, provavelmente repetirá um erro muito comum que o levará ao caminho da insanidade de vinculação de dados.

  • Muitos desenvolvedores tentam usar vinculação de dados para conectar elementos no XAML diretamente a propriedades no modelo.

  • Agora, às vezes isso pode estar bem, mas muitas vezes não é. O problema é que o modelo se preocupa inteiramente com o que o aplicativo faz, e não com a forma como o usuário interage com o aplicativo.

  • A maneira como você apresenta os dados costuma ser um pouco diferente de como são estruturados internamente.

  • Além disso, a maioria das interfaces de usuário tem algum estado que não pertence ao modelo de aplicativo.

  • Por exemplo, se sua interface de usuário usa arrastar e soltar, algo precisa manter o controle de coisas como onde o item que está sendo arrastado está agora, como sua aparência deve mudar conforme ele se move sobre possíveis alvos para soltar e como esses alvos também podem mudar conforme o item é arrastado sobre eles.

  • Esse tipo de estado pode se tornar surpreendentemente complexo e precisa ser totalmente testado.

  • Na prática, você normalmente quer alguma outra aula entre a interface do usuário e o modelo. Isso tem duas funções importantes.

    • Primeiro, ele adapta o modelo do seu aplicativo para uma visão específica da interface do usuário.

    • Em segundo lugar, é onde reside qualquer lógica de interação não trivial e, com isso, quero dizer o código necessário para fazer com que sua interface de usuário se comporte da maneira que você deseja.

O padrão MVVM é, em última análise, a estrutura moderna do padrão MVC, portanto, o objetivo principal ainda é o mesmo: fornecer uma separação clara entre a lógica do domínio e a camada de apresentação. Aqui estão algumas das vantagens e desvantagens do padrão MVVM.

O principal benefício é permitir a verdadeira separação entre a Visualização e o Modelo, além de alcançar a separação e a eficiência que você ganha com isso. O que isso significa em termos reais é que quando seu modelo precisa ser alterado, ele pode ser alterado facilmente sem a necessidade de visualização e vice-versa.

Existem três coisas importantes que fluem da aplicação do MVVM, que são as seguintes.

Capacidade de Manutenção

  • Uma separação clara de diferentes tipos de código deve tornar mais fácil ir para uma ou várias dessas partes mais granulares e focadas e fazer alterações sem se preocupar.

  • Isso significa que você pode permanecer ágil e avançar para novos lançamentos rapidamente.

Testabilidade

  • Com o MVVM, cada parte do código é mais granular e, se for implementado corretamente, suas dependências externas e internas estarão em partes separadas do código das partes com a lógica principal que você gostaria de testar.

  • Isso torna muito mais fácil escrever testes de unidade em uma lógica central.

  • Certifique-se de que funciona bem quando escrito e continua funcionando mesmo quando as coisas mudam na manutenção.

Extensibilidade

  • Às vezes, ele se sobrepõe à capacidade de manutenção, por causa dos limites de separação limpos e partes mais granulares do código.

  • Você tem uma chance melhor de tornar qualquer uma dessas peças mais reutilizável.

  • Ele também tem a capacidade de substituir ou adicionar novos pedaços de código que fazem coisas semelhantes nos lugares certos da arquitetura.

O objetivo óbvio do padrão MVVM é a abstração da Visualização, que reduz a quantidade de lógica de negócios no code-behind. No entanto, a seguir estão algumas outras vantagens sólidas -

  • O ViewModel é mais fácil de testar de unidade do que code-behind ou código orientado a eventos.
  • Você pode testá-lo sem interação e automação de IU inadequada.
  • A camada de apresentação e a lógica são fracamente acopladas.

Desvantagens

  • Algumas pessoas pensam que, para interfaces de usuário simples, o MVVM pode ser um exagero.
  • Da mesma forma, em casos maiores, pode ser difícil projetar o ViewModel.
  • A depuração seria um pouco difícil quando temos ligações de dados complexas.

O padrão MVVM consiste em três partes - Model, View e ViewModel. A maioria dos desenvolvedores no início está um pouco confusa sobre o que um Model, View e ViewModel devem ou não conter e quais são as responsabilidades de cada parte.

Neste capítulo, aprenderemos as responsabilidades de cada parte do padrão MVVM para que você possa entender claramente que tipo de código vai para onde. O MVVM é realmente uma arquitetura em camadas para o lado do cliente, conforme mostrado na figura a seguir.

  • A camada de apresentação é composta pelas visualizações.

  • A camada lógica são os modelos de visualização.

  • A camada de apresentação é a combinação dos objetos do modelo.

  • Os serviços de cliente que os produzem e mantêm ou acesso direcionado em um aplicativo de duas camadas ou por meio de chamadas de serviço e, em seguida, para seu aplicativo.

  • Os serviços do cliente não são oficialmente parte do padrão MVVM, mas são frequentemente usados ​​com o MVVM para conseguir separações adicionais e evitar código duplicado.

Responsabilidades do modelo

Em geral, o modelo é o mais simples de entender. É o modelo de dados do lado do cliente que oferece suporte às visualizações no aplicativo.

  • É composto por objetos com propriedades e algumas variáveis ​​para conter dados na memória.

  • Algumas dessas propriedades podem fazer referência a outros objetos do modelo e criar o gráfico do objeto que, como um todo, são os objetos do modelo.

  • Objetos de modelo devem gerar notificações de alteração de propriedade, o que no WPF significa vinculação de dados.

  • A última responsabilidade é a validação, que é opcional, mas você pode incorporar as informações de validação nos objetos de modelo usando os recursos de validação de vinculação de dados WPF por meio de interfaces como INotifyDataErrorInfo / IDataErrorInfo

Ver responsabilidades

O principal objetivo e responsabilidades das visualizações é definir a estrutura do que o usuário vê na tela. A estrutura pode conter partes estáticas e dinâmicas.

  • As partes estáticas são a hierarquia XAML que define os controles e o layout dos controles dos quais uma exibição é composta.

  • Parte dinâmica é como animações ou mudanças de estado que são definidas como parte da Visualização.

  • O principal objetivo do MVVM é que não haja nenhum código por trás da visualização.

  • É impossível que não haja nenhum código em vista. Em vista, você precisa pelo menos do construtor e de uma chamada para inicializar o componente.

  • A ideia é que o código lógico de manipulação de eventos, ação e manipulação de dados não deva estar no código por trás do View.

  • Existem também outros tipos de código que devem estar no código por trás de qualquer código que seja necessário para ter uma referência ao elemento de interface do usuário que é inerentemente código de exibição.

Responsabilidades do ViewModel

  • ViewModel é o ponto principal do aplicativo MVVM. A principal responsabilidade do ViewModel é fornecer dados para a visualização, para que a visualização possa colocar esses dados na tela.

  • Ele também permite que o usuário interaja com os dados e altere os dados.

  • A outra responsabilidade principal de um ViewModel é encapsular a lógica de interação para uma visualização, mas isso não significa que toda a lógica do aplicativo deve ir para ViewModel.

  • Ele deve ser capaz de lidar com a sequência apropriada de chamadas para fazer a coisa certa acontecer com base no usuário ou em quaisquer alterações na visualização.

  • ViewModel também deve gerenciar qualquer lógica de navegação, como decidir quando é hora de navegar para uma visualização diferente.

Neste capítulo, aprenderemos como usar os padrões MVVM para tela de entrada simples e o aplicativo WPF com o qual você já deve estar acostumado.

Vamos dar uma olhada em um exemplo simples em que usaremos a abordagem MVVM.

Step 1 - Crie um novo projeto de aplicativo WPF MVVMDemo.

Step 2 - Adicione as três pastas (Model, ViewModel e Views) ao seu projeto.

Step 3 - Adicione uma classe StudentModel na pasta Model e cole o código abaixo nessa classe

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged {
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { 
            return firstName; 
         }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName"); 
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string LastName { 
         get {return lastName; } 
			
         set {
            if (lastName != value) { 
               lastName = value;
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
		
      public event PropertyChangedEventHandler PropertyChanged;
		
      private void RaisePropertyChanged(string property) {
         if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
         } 
      } 
   } 
}

Step 4 - Adicione outra classe StudentViewModel na pasta ViewModel e cole o código a seguir.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
				
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Step 5 - Adicione um novo controle de usuário (WPF) clicando com o botão direito na pasta Visualizações e selecione Adicionar> Novo item ...

Step 6- Clique no botão Adicionar. Agora você verá o arquivo XAML. Adicione o seguinte código ao arquivo StudentView.xaml que contém diferentes elementos de IU.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
		
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/>
								
                  </StackPanel> 
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
				
         </ItemsControl> 
			
      </StackPanel> 
   </Grid> 
	
</UserControl>

Step 7 - Agora adicione o StudentView em seu arquivo MainPage.xaml usando o código a seguir.

<Window x:Class = "MVVMDemo.MainWindow"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:local = "clr-namespace:MVVMDemo"
   xmlns:views = "clr-namespace:MVVMDemo.Views"
   mc:Ignorable = "d"
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Grid>
      <views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
   </Grid>
	
</Window>

Step 8 - Aqui está a implementação do evento Loaded no arquivo MainPage.xaml.cs, que atualizará a View do ViewModel.

using System.Windows;

namespace MVVMDemo {

   /// <summary>
      /// Interaction logic for MainWindow.xaml
   /// </summary>
	
   public partial class MainWindow : Window {
	
      public MainWindow() {
         InitializeComponent();
      }
		
      private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
         MVVMDemo.ViewModel.StudentViewModel studentViewModelObject = 
            new MVVMDemo.ViewModel.StudentViewModel();
         studentViewModelObject.LoadStudents();
			
         StudentViewControl.DataContext = studentViewModelObject;
      }
   }
}

Step 9 - Quando o código acima for compilado e executado, você receberá a seguinte saída em sua janela principal.

Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.

Neste capítulo, abordaremos diferentes maneiras de conectar suas visualizações ao ViewModel. Primeiro, vamos dar uma olhada na primeira construção do View, onde podemos declará-lo em XAML. Como vimos o exemplo no último capítulo, em que conectamos uma visualização da janela principal. Agora veremos outras maneiras de conectar visualizações.

Estaremos usando o mesmo exemplo neste capítulo também. A seguir está a mesma implementação da classe Model.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {} 
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string LastName {
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
	
      public event PropertyChangedEventHandler PropertyChanged;
	
      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   }  
}

Aqui está a implementação da classe ViewModel. Desta vez, o método LoadStudents é chamado no construtor padrão.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel{ 

   public class StudentViewModel { 
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Quer a visualização seja uma janela, controle do usuário ou página, o analisador geralmente funciona de cima para baixo e da esquerda para a direita. Ele chama o construtor padrão para cada elemento à medida que o encontra. Existem duas maneiras de construir uma visualização. Você pode usar qualquer um deles.

  • Ver a primeira construção em XAML
  • Ver a primeira construção em code-behind

Ver a primeira construção em XAML

Uma maneira é simplesmente adicionar seu ViewModel como um elemento aninhado no setter para a propriedade DataContext, conforme mostrado no código a seguir.

<UserControl.DataContext> 
   <viewModel:StudentViewModel/> 
</UserControl.DataContext>

Aqui está o arquivo View XAML completo.

<UserControl x:Class="MVVMDemo.Views.StudentView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:local = "clr-namespace:MVVMDemo.Views"
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.DataContext>
      <viewModel:StudentViewModel/>
   </UserControl.DataContext>
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                      <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid> 
	
</UserControl>

Ver a primeira construção em code-behind

Outra maneira é que você pode obter a construção da primeira visualização é simplesmente construir você mesmo o modelo de visualização no código por trás da sua visualização, definindo a propriedade DataContext com a instância.

Normalmente, a propriedade DataContext é definida no método do construtor da visualização, mas você também pode adiar a construção até que o evento Load da visualização seja acionado.

using System.Windows.Controls;

namespace MVVMDemo.Views {
 
   /// <summary> 
      /// Interaction logic for StudentView.xaml 
   /// </summary> 
	
   public partial class StudentView : UserControl { 
      public StudentView() { 
         InitializeComponent(); 
         this.DataContext = new MVVMDemo.ViewModel.StudentViewModel(); 
      } 
   } 
}

Um motivo para construir o modelo de exibição em Code-behind em vez de XAML é que o construtor do modelo de exibição usa parâmetros, mas a análise XAML só pode construir elementos se definido no construtor padrão.

Agora, neste caso, o arquivo XAML de View terá a aparência mostrada no código a seguir.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" 
   d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"<
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Você pode declarar esta Visualização na MainWindow conforme mostrado no arquivo MainWindow.XAML.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
	
   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid> 
	
</Window>

Quando o código acima for compilado e executado, você verá a seguinte saída em sua janela principal.

Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.

Neste capítulo, vamos cobrir como conectar ViewModel. É uma continuação do último capítulo em que discutimos a primeira construção da View. Agora, a próxima forma da primeira construção é ummeta-pattern que é conhecido como ViewModelLocator. É um pseudo padrão e é colocado em camadas sobre o padrão MVVM.

  • No MVVM, cada View precisa ser conectado ao seu ViewModel.

  • O ViewModelLocator é uma abordagem simples para centralizar o código e desacoplar ainda mais a visualização.

  • Isso significa que ele não precisa saber explicitamente sobre o tipo de ViewModel e como construí-lo.

  • Existem várias abordagens diferentes para usar ViewModelLocator, mas aqui usamos a mais semelhante àquela que faz parte da estrutura PRISM.

ViewModelLocator fornece uma maneira padrão, consistente, declarativa e fracamente acoplada de fazer a construção da primeira visualização, que automatiza o processo de conectar o ViewModel à visualização. A figura a seguir representa o processo de alto nível de ViewModelLocator.

Step 1 - Descobrir qual tipo de visualização está sendo construído.

Step 2 - Identifique o ViewModel para esse tipo de View específico.

Step 3 - Construa esse ViewModel.

Step 4 - Defina Views DataContext como ViewModel.

Para entender o conceito básico, vamos dar uma olhada no exemplo simples de ViewModelLocator continuando o mesmo exemplo do último capítulo. Se você olhar o arquivo StudentView.xaml, verá que conectamos estaticamente o ViewModel.

Agora, conforme mostrado no programa a seguir, comente esse código XAML e remova o código do Code-behind.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Agora vamos criar uma nova pasta VML e adicionar uma nova classe pública ViewModelLocator que conterá uma única propriedade anexada (propriedade de dependência) AutoHookedUpViewModel conforme mostrado no código a seguir.

public static bool GetAutoHookedUpViewModel(DependencyObject obj) { 
   return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
}

public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
   obj.SetValue(AutoHookedUpViewModelProperty, value); 
}

// Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
//This enables animation, styling, binding, etc...
 
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
   DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
   typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
   AutoHookedUpViewModelChanged));

E agora você pode ver uma definição básica de propriedade de anexação. Para adicionar comportamento à propriedade, precisamos adicionar um manipulador de eventos alterado para esta propriedade que contém o processo automático de conectar o ViewModel para View. O código para fazer isso é o seguinte -

private static void AutoHookedUpViewModelChanged(DependencyObject d, 
   DependencyPropertyChangedEventArgs e) { 
   if (DesignerProperties.GetIsInDesignMode(d)) return; 
   var viewType = d.GetType(); 
   string str = viewType.FullName; 
   str = str.Replace(".Views.", ".ViewModel."); 
	
   var viewTypeName = str; 
   var viewModelTypeName = viewTypeName + "Model"; 
   var viewModelType = Type.GetType(viewModelTypeName); 
   var viewModel = Activator.CreateInstance(viewModelType);
   ((FrameworkElement)d).DataContext = viewModel; 
}

A seguir está a implementação completa da classe ViewModelLocator.

using System; 
using System.ComponentModel; 
using System.Windows;

namespace MVVMDemo.VML { 

   public static class ViewModelLocator { 
	
      public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
         return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
      }
		
      public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
         obj.SetValue(AutoHookedUpViewModelProperty, value); 
      }
		
      // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
		
      //This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AutoHookedUpViewModelProperty =
         DependencyProperty.RegisterAttached("AutoHookedUpViewModel", 
         typeof(bool), typeof(ViewModelLocator), new
         PropertyMetadata(false, AutoHookedUpViewModelChanged));
		
      private static void AutoHookedUpViewModelChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e) { 
         if (DesignerProperties.GetIsInDesignMode(d)) return; 
         var viewType = d.GetType(); 
			
         string str = viewType.FullName; 
         str = str.Replace(".Views.", ".ViewModel."); 
			
         var viewTypeName = str; 
         var viewModelTypeName = viewTypeName + "Model";
         var viewModelType = Type.GetType(viewModelTypeName); 
         var viewModel = Activator.CreateInstance(viewModelType);
			
        ((FrameworkElement)d).DataContext = viewModel; 
      } 
   } 
}

A primeira coisa a fazer é adicionar um namespace para que possamos chegar a esse tipo de ViewModelLocator na raiz do nosso projeto. Em seguida, no elemento de rota que é um tipo de visualização, adicione a propriedade AutoHookedUpViewModel e defina-a como true.

xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"

Aqui está a implementação completa do arquivo StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
   
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel>
   </Grid> 
	
</UserControl>

Quando o código acima for compilado e executado, você verá que ViewModelLocator está conectando o ViewModel para aquela View em particular.

Uma coisa importante a notar sobre isso é que a visualização não está mais acoplada de forma a qual é o tipo de seu ViewModel ou como ele é construído. Tudo isso foi movido para o local central dentro do ViewModelLocator.

Neste capítulo, aprenderemos como a vinculação de dados oferece suporte ao padrão MVVM. A vinculação de dados é o principal recurso que diferencia o MVVM de outros padrões de separação de IU, como MVC e MVP.

  • Para a vinculação de dados, você precisa ter uma visualização ou conjunto de elementos de interface do usuário construídos e, em seguida, precisa de algum outro objeto para o qual as ligações apontarão.

  • Os elementos da IU em uma visualização são vinculados às propriedades expostas pelo ViewModel.

  • A ordem em que View e ViewModel são construídos depende da situação, pois cobrimos a View primeiro.

  • Um View e ViewModel são construídos e o DataContext do View é definido como ViewModel.

  • As ligações podem ser ligações de dados OneWay ou TwoWay para fluir os dados de um lado para outro entre View e ViewModel.

Vamos dar uma olhada nas ligações de dados no mesmo exemplo. Abaixo está o código XAML de StudentView.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">

   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>--> 

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate>
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid> 

</UserControl>
  • Se você observar o código XAML acima, verá que ItemsControl está vinculado à coleção Students exposta por ViewModel.

  • Você também pode ver que a propriedade do modelo de Aluno também tem seus próprios vínculos individuais, e estes são vinculados a Textboxes e TextBlock.

  • O ItemSource de ItemsControl pode se vincular à propriedade Students, porque o DataContext geral para a View está definido como ViewModel.

  • Os vínculos individuais de propriedades aqui também são vínculos de DataContext, mas não são vinculados ao próprio ViewModel, devido à maneira como um ItemSource funciona.

  • Quando uma fonte de item se liga à sua coleção, ela renderiza um contêiner para cada item na renderização e configura o DataContext desse contêiner para o item. Portanto, o DataContext geral para cada caixa de texto e bloco de texto em uma linha será um Aluno individual da coleção. E você também pode ver que essas associações para TextBoxes são associações de dados TwoWay e para TextBlock são associações de dados OneWay, pois você não pode editar TextBlock.

Ao executar este aplicativo novamente, você verá a seguinte saída.

Vamos agora mudar o texto na segunda caixa de texto da primeira linha de Allain para Upston e pressionar tab para perder o foco. Você verá que o texto TextBlock também é atualizado.

Isso ocorre porque as ligações dos TextBoxes são definidas como TwoWay e ele também atualiza o Model, e a partir do modelo novamente o TextBlock é atualizado.

Um modelo descreve a aparência geral e a aparência visual do controle. Para cada controle, há um modelo padrão associado a ele que dá a aparência a esse controle. No aplicativo WPF, você pode criar facilmente seus próprios modelos quando quiser personalizar o comportamento visual e a aparência visual de um controle. A conectividade entre a lógica e o modelo pode ser alcançada por vinculação de dados.

No MVVM, há outra forma primária que é conhecida como construção inicial do ViewModel.

  • A primeira abordagem de construção do ViewModel alavanca os recursos de modelos de dados implícitos no WPF.

  • Os modelos de dados implícitos podem selecionar automaticamente um modelo apropriado do dicionário de recursos atual para um elemento que usa vinculação de dados. Eles fazem isso com base no tipo de objeto de dados que é processado pela vinculação de dados. Primeiro, você precisa ter algum elemento que esteja vinculado a um objeto de dados.

Vamos dar uma olhada em nosso exemplo simples novamente, no qual você entenderá como pode visualizar o modelo primeiro aproveitando os modelos de dados, especificamente os modelos de dados implícitos. Aqui está a implementação de nossa classe StudentViewModel.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel {
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Você pode ver que o ViewModel acima não foi alterado. Continuaremos com o mesmo exemplo do capítulo anterior. Esta classe ViewModel apenas expõe a propriedade da coleção Students e a preenche na construção. Vamos para o arquivo StudentView.xaml, remover a implementação existente e definir um modelo de dados na seção Recursos.

<UserControl.Resources> 
   <DataTemplate x:Key = "studentsTemplate">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>

Agora adicione uma caixa de listagem e vincule os dados a essa caixa de listagem à propriedade Students conforme mostrado no código a seguir.

<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>

Na seção Resource, o DataTemplate tem uma chave de studentsTemplate e, para realmente usar esse modelo, precisamos usar a propriedade ItemTemplate de um ListBox. Agora você pode ver que instruímos a caixa de listagem a usar esse modelo específico para renderizar esses alunos. A seguir está a implementação completa do arquivo StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate x:Key = "studentsTemplate"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <ListBox 
         ItemsSource = "{Binding Students}" 
         ItemTemplate = "{StaticResource studentsTemplate}"/> 
   </Grid>
	
</UserControl>

Quando o código acima for compilado e executado, você verá a seguinte janela, que contém um ListBox. Cada ListBoxItem contém os dados do objeto da classe Student que são exibidos nas caixas TextBlock e Text.

Para tornar isso um modelo implícito, precisamos remover a propriedade ItemTemplate de uma caixa de listagem e adicionar uma propriedade DataType em nossa definição de modelo, conforme mostrado no código a seguir.

<UserControl.Resources> 
   <DataTemplate DataType = "{x:Type data:Student}">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
				
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>
 
<Grid> 
   <ListBox ItemsSource = "{Binding Students}"/> 
</Grid>

No DataTemplate, a extensão de marcação x: Type é muito importante, semelhante a um tipo de operador em XAML. Portanto, basicamente precisamos apontar para o tipo de dados Student que está no namespace MVVMDemo.Model. A seguir está o arquivo XAML completo atualizado.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid>
      <ListBox ItemsSource = "{Binding Students}"/> 
   </Grid> 
	
</UserControl>

Ao executar este aplicativo novamente, você ainda obterá a mesma renderização dos Alunos com o modelo de dados, porque ele está mapeando automaticamente o tipo do objeto sendo renderizado ao localizar o DataTemplate apropriado.

Recomendamos que você execute o exemplo acima em um método passo a passo para melhor compreensão.

Neste capítulo, aprenderemos como adicionar interatividade aos aplicativos MVVM e como chamar a lógica de forma limpa. Você também verá que tudo isso é feito mantendo o acoplamento fraco e a boa estruturação que é o coração do padrão MVVM. Para entender tudo isso, primeiro vamos aprender sobre os comandos.

Comunicação View / ViewModel por meio de comandos

O padrão de comando foi bem documentado e usa frequentemente o padrão de projeto por algumas décadas. Nesse padrão, existem dois atores principais, o invocador e o receptor.

Invoker

  • O invocador é um pedaço de código que pode executar alguma lógica imperativa.

  • Normalmente, é um elemento de IU com o qual o usuário interage, no contexto de uma estrutura de IU.

  • Pode ser apenas outro pedaço de código lógico em algum outro lugar do aplicativo.

Receptor

  • O receptor é a lógica que deve ser executada quando o invocador é acionado.

  • No contexto do MVVM, o receptor é normalmente um método em seu ViewModel que precisa ser chamado.

Entre esses dois, você tem uma camada de obstrução, o que implica que o invocador e o receptor não precisam se conhecer explicitamente. Isso normalmente é representado como uma abstração de interface exposta ao invocador e uma implementação concreta dessa interface é capaz de chamar o receptor.

Vamos dar uma olhada em um exemplo simples no qual você aprenderá os comandos e como usá-los para se comunicar entre View e ViewModel. Neste capítulo, continuaremos com o mesmo exemplo do capítulo anterior.

No arquivo StudentView.xaml, temos um ListBox que conecta os dados do aluno de um ViewModel. Agora vamos adicionar um botão para excluir um aluno da ListBox.

O importante é que trabalhar com comandos no botão é muito fácil porque eles têm uma propriedade de comando para conectar a um ICommand.

Portanto, podemos expor uma propriedade em nosso ViewModel que tem um ICommand e se vincula a ele a partir da propriedade de comando do botão, conforme mostrado no código a seguir.

<Button Content = "Delete" 
   Command = "{Binding DeleteCommand}" 
   HorizontalAlignment = "Left" 
   VerticalAlignment = "Top" 
   Width = "75" />

Vamos adicionar uma nova classe em seu projeto, que implementará a interface ICommand. A seguir está a implementação da interface ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){ 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }
		
      public void RaiseCanExecuteChanged() { 
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod(); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime 
         is longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 
      public event EventHandler CanExecuteChanged = delegate { };
		
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod(); 
         } 
      } 
   } 
}

Como você pode ver, esta é uma implementação de delegação simples de ICommand, onde temos dois delegados, um para executeMethod e outro para canExecuteMethod, que podem ser transmitidos na construção.

Na implementação acima, existem dois construtores sobrecarregados, um para apenas executeMethod e um para executeMethod e I can canExecuteMethod.

Vamos adicionar uma propriedade do tipo MyICommand na classe StudentView Model. Agora precisamos construir uma instância no StudentViewModel. Usaremos o construtor sobrecarregado de MyICommand que leva dois parâmetros.

public MyICommand DeleteCommand { get; set;} 

public StudentViewModel() { 
   LoadStudents(); 
   DeleteCommand = new MyICommand(OnDelete, CanDelete); 
}

Agora adicione a implementação dos métodos OnDelete e CanDelete.

private void OnDelete() { 
   Students.Remove(SelectedStudent); 
}

private bool CanDelete() { 
   return SelectedStudent != null; 
}

Também precisamos adicionar um novo SelectedStudent para que o usuário possa excluir o Item Selecionado do ListBox.

private Student _selectedStudent;
 
public Student SelectedStudent { 
   get { 
      return _selectedStudent; 
   } 
	
   set { 
      _selectedStudent = value;
      DeleteCommand.RaiseCanExecuteChanged(); 
   } 
}

A seguir está a implementação completa da classe ViewModel.

using MVVMDemo.Model; 

using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;} 
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      }
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get {
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      }
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }
		
      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
   } 
}

Em StudentView.xaml, precisamos adicionar a propriedade SelectedItem em um ListBox que será vinculado à propriedade SelectStudent.

<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>

A seguir está o arquivo xaml completo.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
			
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}" 
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}"
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Quando o código acima for compilado e executado, você verá a seguinte janela.

Você pode ver que o botão de exclusão está desabilitado. Ele será ativado quando você selecionar qualquer item.

Quando você seleciona qualquer item e pressiona delete. Você verá que a lista de itens selecionados foi excluída e o botão de exclusão novamente foi desativado.

Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.

Ao construir aplicativos MVVM, você normalmente decompõe telas complexas de informações em um conjunto de visualizações pai e filho, onde as visualizações filho estão contidas nas visualizações pai em painéis ou controles de contêiner e formam uma hierarquia de uso.

  • Depois de decompor as visualizações complexas, isso não significa que cada parte do conteúdo filho que você separa em seu próprio arquivo XAML precisa necessariamente ser uma visualização MVVM.

  • O pedaço de conteúdo apenas fornece a estrutura para renderizar algo na tela e não oferece suporte a nenhuma entrada ou manipulação pelo usuário para esse conteúdo.

  • Pode não precisar de um ViewModel separado, mas pode ser apenas um fragmento XAML que renderiza com base nas propriedades expostas pelo ViewModel pai.

  • Finalmente, se você tiver uma hierarquia de Views e ViewModels, o ViewModel pai pode se tornar um hub para comunicações, de forma que cada ViewModel filho possa permanecer desacoplado dos outros ViewModels filhos e de seu pai tanto quanto possível.

Vamos dar uma olhada em um exemplo no qual definiremos uma hierarquia simples entre diferentes visualizações. Crie um novo projeto de aplicativo WPFMVVMHierarchiesDemo

Step 1 - Adicione as três pastas (Model, ViewModel e Views) ao seu projeto.

Step 2 - Adicione as classes Customer e Order na pasta Model, CustomerListView e OrderView na pasta Views, e CustomerListViewModel e OrderViewModel na pasta ViewModel, conforme mostrado na imagem a seguir.

Step 3- Adicione blocos de texto em CustomerListView e OrderView. Aqui está o arquivo CustomerListView.xaml.

<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Customer List View"/> 
   </Grid> 
	
</UserControl>

A seguir está o arquivo OrderView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d ="http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Order View"/> 
   </Grid> 
	
</UserControl>

Agora precisamos de algo para hospedar essas visualizações, e um bom lugar para isso em nossa MainWindow porque é um aplicativo simples. Precisamos de um controle de contêiner em que possamos colocar nossas visualizações e alterná-las em um modo de navegação. Para este propósito, precisamos adicionar ContentControl em nosso arquivo MainWindow.xaml e usaremos sua propriedade content e vinculá-la a uma referência ViewModel.

Agora defina os modelos de dados para cada exibição em um dicionário de recursos. A seguir está o arquivo MainWindow.xaml. Observe como cada modelo de dados mapeia um tipo de dados (o tipo ViewModel) para uma Visualização correspondente.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
   
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}"> 
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid> 
      <ContentControl Content = "{Binding CurrentView}"/> 
   </Grid> 
	
</Window>

Sempre que o modelo de exibição atual é definido como uma instância de CustomerListViewModel, ele renderizará um CustomerListView com o ViewModel conectado. É um ViewModel de pedido, ele renderizará OrderView e assim por diante.

Agora precisamos de um ViewModel que tenha uma propriedade CurrentViewModel e alguma lógica e comando para poder mudar a referência atual de ViewModel dentro da propriedade.

Vamos criar um ViewModel para esta MainWindow chamado MainWindowViewModel. Podemos apenas criar uma instância de nosso ViewModel a partir de XAML e usá-la para definir a propriedade DataContext da janela. Para isso, precisamos criar uma classe base para encapsular a implementação de INotifyPropertyChanged para nossos ViewModels.

A ideia principal por trás dessa classe é encapsular a implementação INotifyPropertyChanged e fornecer métodos auxiliares para a classe derivada para que eles possam facilmente acionar as notificações apropriadas. A seguir está a implementação da classe BindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo { 

   class BindableBase : INotifyPropertyChanged { 
	
      protected virtual void SetProperty<T>(ref T member, T val,
         [CallerMemberName] string propertyName = null) { 
            if (object.Equals(member, val)) return;
				
            member = val;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      }
			
      protected virtual void OnPropertyChanged(string propertyName) { 
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
		
      public event PropertyChangedEventHandler PropertyChanged = delegate { }; 
   } 
}

Agora é hora de realmente começar a fazer alguma troca de visualização usando nossa propriedade CurrentViewModel. Precisamos apenas de alguma forma de direcionar a configuração dessa propriedade. E vamos fazer isso de forma que o usuário final possa comandar o acesso à lista de clientes ou à visualização do pedido. Primeiro adicione uma nova classe em seu projeto que implementará a interface ICommand. A seguir está a implementação da interface ICommand.

using System; 
using System.Windows.Input;

namespace MVVMHierarchiesDemo { 

   public class MyICommand<T> : ICommand { 
	
      Action<T> _TargetExecuteMethod; 
      Func<T, bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action<T> executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      } 
		
      #region ICommand Members

      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            T tparm = (T)parameter; 
            return _TargetCanExecuteMethod(tparm); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime is
         longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 

      public event EventHandler CanExecuteChanged = delegate { };
	
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod((T)parameter); 
         } 
      } 
		
      #endregion 
   } 
}

Agora precisamos configurar alguma navegação de nível superior para estes para ViewModels e a lógica para que a troca deve pertencer a MainWindowViewModel. Para isso, vamos usar um método chamado on navigate que pega um destino de string e retorna a propriedade CurrentViewModel.

private void OnNav(string destination) {
 
   switch (destination) { 
      case "orders": 
         CurrentViewModel = orderViewModelModel; 
      break; 
      case "customers": 
      default: 
         CurrentViewModel = custListViewModel; 
      break; 
   } 
}

Para a navegação dessas diferentes visualizações, precisamos adicionar dois botões em nosso arquivo MainWindow.xaml. A seguir está a implementação completa do arquivo XAML.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate> 
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid>
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
	
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions> 
	
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}"
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid> 
	
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" /> 
      </Grid> 
		
   </Grid> 
	
</Window>

A seguir está a implementação completa de MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo {
 
   class MainWindowViewModel : BindableBase {
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 
		
      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
		
      private OrderViewModel orderViewModelModel = new OrderViewModel();
		
      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get {return _CurrentViewModel;} 
         set {SetProperty(ref _CurrentViewModel, value);} 
      }
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) {
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers": 
            default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   } 
}

Derive todos os seus ViewModels da classe BindableBase. Quando o código acima for compilado e executado, você verá a seguinte saída.

Como você pode ver, adicionamos apenas dois botões e um CurrentViewModel em nossa MainWindow. Se você clicar em qualquer botão, ele navegará para aquela Visualização específica. Vamos clicar no botão Clientes e você verá que a CustomerListView é exibida.

Recomendamos que você execute o exemplo acima passo a passo para melhor compreensão.

Neste capítulo, aprenderemos sobre validações. Também veremos uma maneira limpa de fazer validação com o que os vínculos WPF já suportam, mas vinculando-os aos componentes MVVM.

Validação em MVVM

  • Quando seu aplicativo começa a aceitar a entrada de dados de usuários finais, você precisa considerar a validação dessa entrada.

  • Verifique se ele está de acordo com seus requisitos gerais.

  • O WPF tem algumas grandes construções e recursos no sistema de ligação para validar a entrada e você ainda pode aproveitar todos esses recursos ao fazer o MVVM.

  • Lembre-se de que a lógica que suporta sua validação e define quais regras existem para quais propriedades devem fazer parte do Model ou ViewModel, não da própria View.

Você ainda pode usar todas as formas de expressar validação que são suportadas pela vinculação de dados WPF, incluindo -

  • O lançamento de exceções em uma propriedade está definido.
  • Implementando a interface IDataErrorInfo.
  • Implementando INotifyDataErrorInfo.
  • Use as regras de validação WPF.

Em geral, INotifyDataErrorInfo é recomendado e foi introduzido no WPF .net 4.5 e oferece suporte à consulta de erros associados a propriedades no objeto e também corrige algumas deficiências com todas as outras opções. Especificamente, ele permite a validação assíncrona. Ele permite que as propriedades tenham mais de um erro associado a elas.

Adicionando Validação

Vamos dar uma olhada em um exemplo no qual adicionaremos suporte de validação à nossa visualização de entrada e, em aplicativos grandes, você provavelmente precisará disso em vários lugares em seu aplicativo. Às vezes em Views, às vezes em ViewModels e às vezes nesses objetos auxiliares, existem invólucros em torno dos objetos de modelo.

É uma boa prática colocar o suporte de validação em uma classe base comum que você pode herdar de diferentes cenários.

A classe base suportará INotifyDataErrorInfo para que a validação seja acionada quando as propriedades forem alteradas.

Crie uma nova classe chamada ValidatableBindableBase. Como já temos uma classe base para um tratamento de alteração de propriedade, vamos derivar a classe base dela e também implementar a interface INotifyDataErrorInfo.

A seguir está a implementação da classe ValidatableBindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

//using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text;
using System.Threading.Tasks; 
using System.Windows.Controls;

namespace MVVMHierarchiesDemo { 

   public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo { 
      private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

      public event EventHandler<DataErrorsChangedEventArgs> 
         ErrorsChanged = delegate { };

      public System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      public bool HasErrors { 
         get { return _errors.Count > 0; } 
      }
		
      protected override void SetProperty<T>(ref T member, T val, 
         [CallerMemberName] string propertyName = null) {
		
         base.SetProperty<T>(ref member, val, propertyName);
         ValidateProperty(propertyName, val);
      }
		
      private void ValidateProperty<T>(string propertyName, T value) {
         var results = new List<ValidationResult>();
			
         //ValidationContext context = new ValidationContext(this); 
         //context.MemberName = propertyName;
         //Validator.TryValidateProperty(value, context, results);

         if (results.Any()) {
            //_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
         } else { 
            _errors.Remove(propertyName); 
         }
			
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
      } 
   } 
}

Agora adicione AddEditCustomerView e AddEditCustomerViewModel nas respectivas pastas. A seguir está o código de AddEditCustomerView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

      <Grid Grid.Row = "1"> 
         <Button Content = "Save" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" />
		
         <Button Content = "Add" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
		
         <Button Content = "Cancel" 
            Command = "{Binding CancelCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "150,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </Grid>
		
   </Grid> 
	
</UserControl>

A seguir está a implementação AddEditCustomerViewModel.

using MVVMHierarchiesDemo.Model;

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
	
      public AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      public void SetCustomer(Customer cust) {
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }
		
      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; }
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         Done(); 
      }
		
      private bool CanSave() { 
         return !Customer.HasErrors; 
      }  
   } 
}

A seguir está a implementação da classe SimpleEditableCustomer.

using System;
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Model { 

   public class SimpleEditableCustomer : ValidatableBindableBase { 
      private Guid _id; 
		
      public Guid Id { 
         get { return _id; } 
         set { SetProperty(ref _id, value); } 
      }
		
      private string _firstName; 
      [Required]
		
      public string FirstName { 
         get { return _firstName; } 
         set { SetProperty(ref _firstName, value); } 
      }
		
      private string _lastName; 
      [Required] 
		
      public string LastName {  
         get { return _lastName; } 
         set { SetProperty(ref _lastName, value); } 
      }
		
      private string _email; 
      [EmailAddress] 
		
      public string Email {
         get { return _email; } 
         set { SetProperty(ref _email, value); } 
      }
		
      private string _phone; 
      [Phone] 
		
      public string Phone { 
         get { return _phone; } 
         set { SetProperty(ref _phone, value); } 
      } 
   } 
}

Quando o código acima for compilado e executado, você verá a seguinte janela.

Ao pressionar o botão Adicionar cliente, você verá a seguinte exibição. Quando o usuário deixar algum campo vazio, ele ficará destacado e o botão Salvar ficará desabilitado.

Neste capítulo, discutiremos brevemente sobre injeção de dependência. Já cobrimos a vinculação de dados que separa Views e ViewModels uns dos outros, o que permite que eles se comuniquem sem saber explicitamente o que está acontecendo na outra extremidade da comunicação.

Agora precisamos de algo semelhante para desacoplar nosso ViewModel dos serviços do cliente.

Nos primeiros dias da programação orientada a objetos, os desenvolvedores enfrentaram o problema de criar e recuperar instâncias de classes em aplicativos. Várias soluções têm sido propostas para este problema.

Nos últimos anos, injeção de dependência e inversão de controle (IoC) ganharam popularidade entre os desenvolvedores e têm precedência sobre algumas soluções mais antigas, como o padrão Singleton.

Dependency Injection / IoC Containers

IoC e injeção de dependência são dois padrões de design intimamente relacionados e o contêiner é basicamente um pedaço de código de infraestrutura que faz os dois padrões para você.

  • O padrão IoC trata de delegar responsabilidade pela construção e o padrão de injeção de dependência trata de fornecer dependências a um objeto que já foi construído.

  • Ambos podem ser tratados como uma abordagem de construção em duas fases. Quando você usa um contêiner, o contêiner assume várias responsabilidades, que são as seguintes -

    • Ele constrói um objeto quando solicitado.
    • O contêiner determinará do que esse objeto depende.
    • Construindo essas dependências.
    • Injetando-os no objeto sendo construído.
    • Fazendo processo recursivamente.

Vamos dar uma olhada em como podemos usar injeção de dependência para quebrar o desacoplamento entre ViewModels e os serviços do cliente. Iremos conectar o formulário AddEditCustomerViewModel de manipulação de salvamento usando injeção de dependência relacionada a isso.

Primeiro, precisamos criar uma nova interface em nosso projeto na pasta Services. Se você não tiver uma pasta de serviços em seu projeto, crie-a primeiro e adicione a seguinte interface na pasta Serviços.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public interface ICustomersRepository { 
      Task<List<Customer>> GetCustomersAsync(); 
      Task<Customer> GetCustomerAsync(Guid id); 
      Task<Customer> AddCustomerAsync(Customer customer); 
      Task<Customer> UpdateCustomerAsync(Customer customer); 
      Task DeleteCustomerAsync(Guid customerId); 
   } 
}

A seguir está a implementação de ICustomersRepository.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public class CustomersRepository : ICustomersRepository {
      ZzaDbContext _context = new ZzaDbContext();

      public Task<List<Customer>> GetCustomersAsync() { 
         return _context.Customers.ToListAsync(); 
      }

      public Task<Customer> GetCustomerAsync(Guid id) { 
         return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); 
      }
		
      public async Task<Customer> AddCustomerAsync(Customer customer){ 
         _context.Customers.Add(customer); 
         await _context.SaveChangesAsync(); 
         return customer;
      }

      public async Task<Customer> UpdateCustomerAsync(Customer customer) {
		
         if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { 
            _context.Customers.Attach(customer); 
         } 
			
         _context.Entry(customer).State = EntityState.Modified;
         await _context.SaveChangesAsync(); 
         return customer;
			
      }

      public async Task DeleteCustomerAsync(Guid customerId) {
         var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); 
			
         if (customer != null) {
            _context.Customers.Remove(customer); 
         }
			
         await _context.SaveChangesAsync(); 
      } 
   } 
}

A maneira simples de fazer o tratamento de Salvar é adicionar uma nova instância de ICustomersRepository em AddEditCustomerViewModel e sobrecarregar o construtor AddEditCustomerViewModel e CustomerListViewModel.

private ICustomersRepository _repo; 

public AddEditCustomerViewModel(ICustomersRepository repo) { 
   _repo = repo; 
   CancelCommand = new MyIcommand(OnCancel);
   SaveCommand = new MyIcommand(OnSave, CanSave); 
}

Atualize o método OnSave conforme mostrado no código a seguir.

private async void OnSave() { 
   UpdateCustomer(Customer, _editingCustomer); 
	
   if (EditMode) 
      await _repo.UpdateCustomerAsync(_editingCustomer); 
   else 
      await _repo.AddCustomerAsync(_editingCustomer); 
   Done(); 
} 

private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
   target.FirstName = source.FirstName; 
   target.LastName = source.LastName; 
   target.Phone = source.Phone; 
   target.Email = source.Email; 
}

A seguir está o AddEditCustomerViewModel completo.

using MVVMHierarchiesDemo.Model; 
using MVVMHierarchiesDemo.Services; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
      private ICustomersRepository _repo; 
		
      public AddEditCustomerViewModel(ICustomersRepository repo) { 
         _repo = repo;
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value); } 
      }

      private SimpleEditableCustomer _Customer; 
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value); } 
      }
		
      private Customer _editingCustomer = null;

      public void SetCustomer(Customer cust) { 
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }

      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; } 
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         UpdateCustomer(Customer, _editingCustomer); 
			
         if (EditMode) 
            await _repo.UpdateCustomerAsync(_editingCustomer); 
         else 
            await _repo.AddCustomerAsync(_editingCustomer); 
         Done(); 
      }

      private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
         target.FirstName = source.FirstName; 
         target.LastName = source.LastName; 
         target.Phone = source.Phone; 
         target.Email = source.Email; 
      }

      private bool CanSave() { 
         return !Customer.HasErrors; 
      }
		
      private void CopyCustomer(Customer source, SimpleEditableCustomer target) { 
         target.Id = source.Id; 
			
         if (EditMode) { 
            target.FirstName = source.FirstName; 
            target.LastName = source.LastName; 
            target.Phone = source.Phone; 
            target.Email = source.Email; 
         }
      } 
   } 
}

Quando o código acima for compilado e executado, você verá a mesma saída, mas agora os ViewModels são desacoplados de maneira mais flexível.

Ao pressionar o botão Adicionar cliente, você verá a seguinte exibição. Quando o usuário deixar algum campo vazio, ele ficará destacado e o botão Salvar ficará desabilitado.

Um evento é uma construção de programação que reage a uma mudança de estado, notificando quaisquer terminais que tenham se registrado para notificação. Principalmente, os eventos são usados ​​para informar a entrada do usuário por meio do mouse e do teclado, mas sua utilidade não se limita a isso. Sempre que uma mudança de estado é detectada, talvez quando um objeto foi carregado ou inicializado, um evento pode ser disparado para alertar quaisquer terceiros interessados.

  • Em um aplicativo WPF que usa o padrão de design MVVM (Model-View-ViewModel), o modelo de exibição é o componente responsável por manipular a lógica de apresentação e o estado do aplicativo.

  • O arquivo code-behind da visualização não deve conter nenhum código para manipular eventos que são gerados a partir de qualquer elemento da interface do usuário (UI), como um botão ou um ComboBox, nem deve conter qualquer lógica específica de domínio.

  • Idealmente, o code-behind de uma View contém apenas um construtor que chama o método InitializeComponent e talvez algum código adicional para controlar ou interagir com a camada de view que é difícil ou ineficiente de expressar em XAML, por exemplo, animações complexas.

Vamos dar uma olhada em um exemplo simples de eventos de clique de botão em nosso aplicativo. A seguir está o código XAML do arquivo MainWindow.xaml no qual você verá dois botões.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		 
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/>
      </DataTemplate> 
   </Window.Resources> 

   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
		
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" />
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions>
			
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid>
		
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" />
      </Grid> 
		
   </Grid> 

</Window>

Você pode ver que a propriedade Click do botão não é usada no arquivo XAML acima, mas as propriedades Command e CommandParameter são usadas para carregar exibições diferentes quando o botão é pressionado. Agora você precisa definir a implementação dos comandos no arquivo MainWindowViewModel.cs, mas não no arquivo View. A seguir está a implementação completa de MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo { 

   class MainWindowViewModel : BindableBase { 
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 

      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
      private OrderViewModel orderViewModelModel = new OrderViewModel();

      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get { return _CurrentViewModel; } 
         set { SetProperty(ref _CurrentViewModel, value); } 
      } 
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) { 
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers":
               default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   }
}

Derive todos os seus ViewModels da classe BindableBase. Quando o código acima for compilado e executado, você verá a seguinte saída.

Como você pode ver, adicionamos apenas dois botões e um CurrentViewModel em nossa MainWindow. Agora, se você clicar em qualquer botão, ele navegará para aquela Visualização específica. Vamos clicar no botão Clientes e você verá que a CustomerListView é exibida.

Recomendamos que você execute o exemplo acima em um método passo a passo para melhor compreensão.

A ideia por trás do teste de unidade é pegar pedaços discretos de código (unidades) e escrever métodos de teste que usem o código de maneira esperada e, em seguida, testar para ver se obtêm os resultados esperados.

  • Sendo o próprio código, os testes de unidade são compilados assim como o resto do projeto.

  • Eles também são executados pelo software de teste, que pode acelerar cada teste, dando os polegares para cima ou para baixo para indicar se o teste foi aprovado ou reprovado, respectivamente.

Vamos dar uma olhada em um exemplo criado anteriormente. A seguir está a implementação do Modelo do Aluno.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;

      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         }
      }

      public string LastName { 
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName");
            } 
         } 
      }

      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }

      public event PropertyChangedEventHandler PropertyChanged;

      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   } 
}

A seguir está a implementação de StudentView.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">

   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>

   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}"
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}" 
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>

</UserControl>

A seguir está a implementação de StudentViewModel.

using MVVMDemo.Model;
 
using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;}
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }

      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }

      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();

         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
			
         Students = students; 
      } 
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get { 
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      } 
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }

      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
		
      public int GetStudentCount() { 
         return Students.Count; 
      } 
   } 
}

A seguir está o arquivo MainWindow.xaml.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid>
 
</Window>

A seguir está a implementação de MyICommand, que implementa a interface ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;

      public MyICommand(Action executeMethod) { 
         _TargetExecuteMethod = executeMethod; 
      }

      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) { 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod();
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime
         is longer than lifetime of UI objects that get hooked up to command
			
      // Prism commands solve this in their implementation
		
      public event EventHandler CanExecuteChanged = delegate { };

      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) { 
            _TargetExecuteMethod(); 
         } 
      } 
   }
}

Quando o código acima for compilado e executado, você verá a seguinte saída em sua janela principal.

Para escrever um teste de unidade para o exemplo acima, vamos adicionar um novo Projeto de Teste à Solução.

Adicione referência ao projeto clicando com o botão direito em Referências.

Selecione o projeto existente e clique em OK.

Vamos agora adicionar um teste simples que verificará a contagem de alunos conforme mostrado no código a seguir.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] 
		
      public void TestMethod1() { 
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 3); 
      } 
   } 
}

Para executar este teste, selecione a opção de menu Teste → Executar → Todos os testes.

Você pode ver no Test Explorer que o teste foi aprovado, porque no StudentViewModel, três alunos são adicionados. Altere a condição de contagem de 3 para 4, conforme mostrado no código a seguir.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] public void TestMethod1() {
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 4);
      } 
   } 
}

Quando o plano de teste for executado novamente, você verá que o teste falhou porque a contagem de alunos não é igual a 4.

Recomendamos que você execute o exemplo acima em um método passo a passo para melhor compreensão.

Neste capítulo, discutiremos os kits de ferramentas ou estruturas MVVM disponíveis. Você também pode usar essas estruturas para que não precise escrever um monte de código repetitivo para implementar o padrão MVVM sozinho. Aqui estão algumas das estruturas mais populares -

  • Prism
  • MVVM Light
  • Caliburn Micro

Prisma

O Prism fornece orientação na forma de exemplos e documentação que ajudam você a projetar e construir facilmente aplicativos de desktop Windows Presentation Foundation (WPF) ricos, flexíveis e de fácil manutenção. Rich Internet Applications (RIAs) criados com o plug-in do navegador Microsoft Silverlight e aplicativos do Windows.

  • O Prism usa padrões de design que incorporam princípios de design arquitetônico importantes, como separação de interesses e acoplamento fraco.

  • O Prism ajuda você a projetar e construir aplicativos usando componentes fracamente acoplados que podem evoluir de forma independente, mas que podem ser facilmente e perfeitamente integrados ao aplicativo geral.

  • Esses tipos de aplicativos são conhecidos como aplicativos compostos.

O Prism possui vários recursos prontos para uso. A seguir estão alguns dos recursos importantes do Prism.

Padrão MVVM

O Prism tem suporte para o padrão MVVM. Ele tem uma classe Bindablebase semelhante à que foi implementada nos capítulos anteriores.

Ele tem um ViewModelLocator flexível que possui convenções, mas permite que você substitua essas convenções e conecte declarativamente suas Views e ViewModels de uma forma fracamente acoplada.

Modularidade

É a capacidade de dividir o código em partes em bibliotecas de classes totalmente fracamente acopladas e reuni-las no tempo de execução em um todo coeso para o usuário final, enquanto o código permanece totalmente desacoplado.

Composição / Regiões da IU

É a capacidade de conectar visualizações em contêineres sem a Visualização que está fazendo a conexão, precisando ter uma referência explícita ao próprio contêiner de UI.

Navegação

O Prism possui recursos de navegação que se sobrepõem às regiões, como navegação para frente e para trás e a pilha de navegação que permite que seus modelos de visualização participem diretamente do processo de navegação.

Comandos

O Prism tem comandos, então eles têm um comando delegado que é muito semelhante ao MyICommand que usamos nos capítulos anteriores, exceto que tem alguma robustez extra para protegê-lo de vazamentos de memória.

Eventos Pub / Sub

O Prism também oferece suporte para eventos Pub / Sub. Esses são eventos fracamente acoplados em que o editor e o assinante podem ter tempos de vida diferentes e não precisam ter referências explícitas um ao outro para se comunicar por meio de eventos.

MVVM Light

MVVM Light é produzido por Laurent Bugnion e ajuda a separar sua View de seu Model, o que cria aplicativos mais limpos e fáceis de manter e estender.

  • Ele também cria aplicativos testáveis ​​e permite que você tenha uma camada de interface do usuário muito mais fina (que é mais difícil de testar automaticamente).

  • Este kit de ferramentas coloca ênfase especial na abertura e edição da interface do usuário no Blend, incluindo a criação de dados em tempo de design para permitir que os usuários do Blend "vejam algo" quando trabalham com controles de dados.

Caliburn Micro

Esta é outra pequena estrutura de código aberto que ajuda a implementar o padrão MVVM e também oferece suporte a uma série de coisas prontas para usar.

  • Caliburn Micro é uma estrutura pequena, mas poderosa, projetada para criar aplicativos em todas as plataformas XAML.

  • Com forte suporte para MVVM e outros padrões de IU comprovados, o Caliburn Micro permitirá que você crie sua solução rapidamente, sem a necessidade de sacrificar a qualidade do código ou testabilidade.