В этой главе мы узнаем, как добавить интерактивность в приложения MVVM и как правильно вызывать логику. Вы также увидите, что все это достигается путем поддержания слабой связи и хорошей структуризации, которая является сердцем шаблона MVVM. Чтобы понять все это, сначала давайте узнаем о командах.
View / ViewModel Связь с помощью команд
Шаблон команд хорошо документирован и часто использует шаблон проектирования в течение нескольких десятилетий. В этом паттерне есть два главных актера, призыватель и получатель.
Чешуи
-
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>
Когда приведенный выше код скомпилирован и выполнен, вы увидите следующее окно.
Вы можете видеть, что кнопка удаления отключена. Он будет включен при выборе любого элемента.
Когда вы выбираете любой элемент и нажимаете удалить. Вы увидите, что выбранный список элементов удален, а кнопка удаления снова отключена.
Мы рекомендуем выполнить вышеприведенный пример пошагово для лучшего понимания.