MVVM - Comunicação View / ViewModel

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 possui 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, há 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.