MVVM - Komunikacja View / ViewModel
W tym rozdziale dowiemy się, jak dodać interaktywność do aplikacji MVVM i jak czysto wywoływać logikę. Zobaczysz również, że wszystko to odbywa się poprzez zachowanie luźnego powiązania i dobrej strukturyzacji, która jest sercem wzorca MVVM. Aby to wszystko zrozumieć, najpierw poznajmy polecenia.
Komunikacja View / ViewModel za pomocą poleceń
Wzorzec poleceń został dobrze udokumentowany i od kilku dekad często wykorzystuje wzorzec projektowy. W tym wzorze występuje dwóch głównych aktorów, wywołujący i odbierający.
Invoker
Wywołujący jest fragmentem kodu, który może wykonać jakąś imperatywną logikę.
Zwykle jest to element interfejsu użytkownika, z którym użytkownik wchodzi w interakcje w kontekście struktury interfejsu użytkownika.
Może to być po prostu kolejny fragment kodu logicznego w innym miejscu aplikacji.
Odbiorca
Odbiornik jest logiką, która jest przeznaczona do wykonania, gdy wywołujący odpala.
W kontekście MVVM odbiornikiem jest zwykle metoda w Twoim ViewModel, którą należy wywołać.
Pomiędzy tymi dwoma masz warstwę przeszkód, co oznacza, że wywołujący i odbierający nie muszą o sobie jawnie wiedzieć. Zwykle jest to reprezentowane jako abstrakcja interfejsu ujawniona wywołującemu, a konkretna implementacja tego interfejsu jest w stanie wywołać odbiorcę.
Rzućmy okiem na prosty przykład, w którym nauczysz się poleceń i jak ich używać do komunikacji między widokiem a ViewModel. W tym rozdziale będziemy kontynuować ten sam przykład z poprzedniego rozdziału.
W pliku StudentView.xaml mamy ListBox, który łączy dane uczniów z ViewModel. Teraz dodajmy przycisk do usuwania ucznia z ListBox.
Ważną rzeczą jest to, że praca z poleceniami na przycisku jest bardzo łatwa, ponieważ mają one właściwość command do podłączenia do ICommand.
Możemy więc uwidocznić właściwość w naszym ViewModel, która ma ICommand i jest z nią powiązana z właściwości polecenia przycisku, jak pokazano w poniższym kodzie.
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
Dodajmy do projektu nową klasę, która zaimplementuje interfejs ICommand. Poniżej przedstawiono implementację interfejsu 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();
}
}
}
}
Jak widać, jest to prosta implementacja delegowania ICommand, w której mamy dwóch delegatów, jednego dla executeMethod, a drugiego dla canExecuteMethod, które można przekazać podczas konstrukcji.
W powyższej implementacji istnieją dwa przeciążone konstruktory, jeden tylko dla executeMethod i jeden dla obu executeMethod i I canExecuteMethod.
Dodajmy właściwość typu MyICommand w klasie StudentView Model. Teraz musimy skonstruować wystąpienie w StudentViewModel. Użyjemy przeciążonego konstruktora MyICommand, który przyjmuje dwa parametry.
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
Teraz dodaj implementację metod OnDelete i CanDelete.
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
Musimy również dodać nowego SelectedStudent, aby użytkownik mógł usunąć wybrany element z ListBox.
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
Poniżej znajduje się pełna implementacja klasy 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;
}
}
}
W StudentView.xaml musimy dodać właściwość SelectedItem w ListBox, która zostanie powiązana z właściwością SelectStudent.
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
Poniżej znajduje się pełny plik 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>
Kiedy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące okno.
Widać, że przycisk usuwania jest wyłączony. Zostanie włączony po wybraniu dowolnego elementu.
Po wybraniu dowolnego elementu i naciśnięciu klawisza usuń. Zobaczysz, że wybrana lista pozycji została usunięta, a przycisk usuwania zostanie ponownie wyłączony.
Zalecamy wykonanie powyższego przykładu krok po kroku w celu lepszego zrozumienia.