Учебники

MVVM — View / ViewModel Communication

В этой главе мы узнаем, как добавить интерактивность в приложения MVVM и как правильно вызывать логику. Вы также увидите, что все это достигается путем поддержания слабой связи и хорошей структуризации, которая является сердцем шаблона MVVM. Чтобы понять все это, сначала давайте узнаем о командах.

View / ViewModel Связь с помощью команд

Шаблон команд хорошо документирован и часто использует шаблон проектирования в течение нескольких десятилетий. В этом паттерне есть два главных актера, призыватель и получатель.

View and ViewModel Communication

Чешуи

  • Invoker — это фрагмент кода, который может выполнять некоторую императивную логику.

  • Как правило, это элемент пользовательского интерфейса, с которым пользователь взаимодействует в контексте инфраструктуры пользовательского интерфейса.

  • Это может быть просто еще один кусок логического кода где-то еще в приложении.

Invoker — это фрагмент кода, который может выполнять некоторую императивную логику.

Как правило, это элемент пользовательского интерфейса, с которым пользователь взаимодействует в контексте инфраструктуры пользовательского интерфейса.

Это может быть просто еще один кусок логического кода где-то еще в приложении.

Получатель

  • Приемник — это логика, предназначенная для выполнения при срабатывании инициатора.

  • В контексте MVVM получатель — это обычно метод в вашей ViewModel, который необходимо вызвать.

Приемник — это логика, предназначенная для выполнения при срабатывании инициатора.

В контексте MVVM получатель — это обычно метод в вашей ViewModel, который необходимо вызвать.

Между этими двумя уровнями имеется слой препятствий, который подразумевает, что инициатор и получатель не должны явно знать друг о друге. Обычно это представляется как абстракция интерфейса, доступная для вызывающего, и конкретная реализация этого интерфейса способна вызвать получателя.

Давайте рассмотрим простой пример, в котором вы изучите команды и как их использовать для связи между View и ViewModel. В этой главе мы продолжим с тем же примером из предыдущей главы.

В файле StudentView.xaml у нас есть ListBox, который подключает данные ученика из ViewModel. Теперь давайте добавим кнопку для удаления студента из ListBox.

Важно то, что работать с командами для кнопки очень легко, потому что у них есть свойство команды для подключения к ICommand.

Таким образом, мы можем предоставить свойство нашей ViewModel, которое имеет ICommand и связывается с ним из свойства команды кнопки, как показано в следующем коде.

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

Давайте добавим новый класс в ваш проект, который будет реализовывать интерфейс ICommand. Ниже приведена реализация интерфейса 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(); 
         } 
      } 
   } 
}

Как вы можете видеть, это простая делегирующая реализация ICommand, где у нас есть два делегата, один для executeMethod и один для canExecuteMethod, который можно передать при создании.

В вышеприведенной реализации есть два перегруженных конструктора, один только для executeMethod и один для обоих executeMethod, и я могу canExecuteMethod.

Давайте добавим свойство типа MyICommand в класс модели StudentView. Теперь нам нужно создать экземпляр в StudentViewModel. Мы будем использовать перегруженный конструктор MyICommand, который принимает два параметра.

public MyICommand DeleteCommand { get; set;} 

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

Теперь добавьте реализацию методов OnDelete и CanDelete.

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

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

Нам также нужно добавить новый SelectedStudent, чтобы пользователь мог удалить выбранный элемент из ListBox.

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

Ниже приводится полная реализация класса 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; 
      }
   } 
}

В StudentView.xaml нам нужно добавить свойство SelectedItem в ListBox, которое будет привязано к свойству SelectStudent.

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

Ниже приведен полный файл 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: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>

Когда приведенный выше код скомпилирован и выполнен, вы увидите следующее окно.

View and ViewModel Communication MainWindow1

Вы можете видеть, что кнопка удаления отключена. Он будет включен при выборе любого элемента.

View и ViewModel Связь MainWindow2

Когда вы выбираете любой элемент и нажимаете удалить. Вы увидите, что выбранный список элементов удален, а кнопка удаления снова отключена.

View и ViewModel Связь MainWindow3

Мы рекомендуем выполнить вышеприведенный пример пошагово для лучшего понимания.