MVVM - Comunicazione View / ViewModel
In questo capitolo impareremo come aggiungere interattività alle applicazioni MVVM e come chiamare in modo pulito la logica. Vedrai anche che tutto questo viene fatto mantenendo l'accoppiamento libero e una buona strutturazione che è il cuore del pattern MVVM. Per capire tutto questo, impariamo prima i comandi.
Comunicazione View / ViewModel tramite comandi
Il modello di comando è stato ben documentato e utilizza spesso il modello di progettazione per un paio di decenni. In questo schema ci sono due attori principali, l'invocatore e il ricevente.
Invoker
L'invoker è un pezzo di codice che può eseguire una logica imperativa.
In genere, è un elemento dell'interfaccia utente con cui l'utente interagisce, nel contesto di un framework dell'interfaccia utente.
Potrebbe essere solo un altro pezzo di codice logico da qualche altra parte nell'applicazione.
Ricevitore
Il ricevitore è la logica destinata all'esecuzione quando l'invocatore viene attivato.
Nel contesto di MVVM, il ricevitore è in genere un metodo nel ViewModel che deve essere chiamato.
Tra questi due, c'è un livello di ostruzione, il che implica che l'invocatore e il destinatario non devono conoscersi esplicitamente l'uno dell'altro. Questo è tipicamente rappresentato come un'astrazione dell'interfaccia esposta al chiamante e un'implementazione concreta di tale interfaccia è in grado di chiamare il ricevitore.
Diamo un'occhiata a un semplice esempio in cui imparerai i comandi e come usarli per comunicare tra View e ViewModel. In questo capitolo, continueremo con lo stesso esempio dell'ultimo capitolo.
Nel file StudentView.xaml, abbiamo un ListBox che collega i dati degli studenti da un ViewModel. Ora aggiungiamo un pulsante per eliminare uno studente dalla ListBox.
La cosa importante è che lavorare con i comandi sul pulsante è molto semplice perché hanno una proprietà di comando da collegare a un ICommand.
Quindi, possiamo esporre una proprietà sul nostro ViewModel che ha un ICommand e si associa ad esso dalla proprietà del comando del pulsante come mostrato nel codice seguente.
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
Aggiungiamo una nuova classe nel tuo progetto, che implementerà l'interfaccia ICommand. Di seguito è riportata l'implementazione dell'interfaccia 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();
}
}
}
}
Come puoi vedere, questa è una semplice implementazione di delega di ICommand in cui abbiamo due delegati uno per executeMethod e uno per canExecuteMethod che possono essere passati in fase di costruzione.
Nell'implementazione precedente, ci sono due costruttori sovraccaricati, uno per solo executeMethod e uno per executeMethod e I canExecuteMethod.
Aggiungiamo una proprietà di tipo MyICommand nella classe StudentView Model. Ora dobbiamo costruire un'istanza in StudentViewModel. Useremo il costruttore sovraccarico di MyICommand che accetta due parametri.
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
Ora aggiungi l'implementazione dei metodi OnDelete e CanDelete.
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
È inoltre necessario aggiungere un nuovo SelectedStudent in modo che l'utente possa eliminare l'elemento selezionato da ListBox.
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
Di seguito è riportata l'implementazione completa della 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;
}
}
}
In StudentView.xaml, dobbiamo aggiungere la proprietà SelectedItem in una ListBox che si legherà alla proprietà SelectStudent.
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
Di seguito è riportato il file 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 il codice sopra è stato compilato ed eseguito, vedrai la seguente finestra.
Puoi vedere che il pulsante Elimina è disabilitato. Sarà abilitato quando selezioni un elemento.
Quando selezioni un elemento e premi Elimina. Vedrai che l'elenco degli elementi selezionati viene eliminato e il pulsante Elimina viene nuovamente disabilitato.
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.