MVVM - связь View / ViewModel
В этой главе мы узнаем, как добавить интерактивности в приложения MVVM и как чисто вызывать логику. Вы также увидите, что все это достигается за счет сохранения слабой связи и хорошей структуризации, которые являются сердцем шаблона MVVM. Чтобы разобраться во всем этом, сначала давайте узнаем о командах.
Связь View / ViewModel с помощью команд
Командный шаблон хорошо документирован и часто использует шаблон проектирования в течение нескольких десятилетий. В этом шаблоне есть два основных участника: вызывающий и получатель.
Invoker
Вызывающий объект - это кусок кода, который может выполнять некоторую императивную логику.
Обычно это элемент пользовательского интерфейса, с которым взаимодействует пользователь в контексте инфраструктуры пользовательского интерфейса.
Это может быть просто еще один фрагмент логического кода где-то еще в приложении.
Приемник
Получатель - это логика, которая предназначена для выполнения при срабатывании вызывающей стороны.
В контексте MVVM получателем обычно является метод в вашей модели представления, который необходимо вызвать.
Между этими двумя у вас есть уровень препятствий, который подразумевает, что вызывающий и получатель не должны явно знать друг о друге. Обычно это представляется как абстракция интерфейса, доступная вызывающей стороне, и конкретная реализация этого интерфейса способна вызывать получатель.
Давайте рассмотрим простой пример, в котором вы узнаете команды и способы их использования для связи между View и ViewModel. В этой главе мы продолжим с того же примера из предыдущей главы.
В файле StudentView.xaml у нас есть ListBox, который подключает данные учащихся из ViewModel. Теперь добавим кнопку для удаления студента из ListBox.
Важно то, что работать с командами на кнопке очень просто, потому что у них есть свойство command для подключения к 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 Model. Теперь нам нужно создать экземпляр в 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>
Когда приведенный выше код скомпилирован и запущен, вы увидите следующее окно.
Вы можете видеть, что кнопка удаления отключена. Он будет включен при выборе любого пункта.
Когда вы выбираете любой элемент и нажимаете удалить. Вы увидите, что выбранный список элементов удален, и кнопка удаления снова станет недоступной.
Мы рекомендуем вам выполнить приведенный выше пример пошагово для лучшего понимания.