Modello Model-View-ViewModel
Nota
Questo eBook è stato pubblicato nella primavera del 2017 e non è stato aggiornato da allora. C'è molto nel libro che rimane prezioso, ma alcuni dei materiali sono obsoleti.
L'esperienza Xamarin.Forms di sviluppo prevede in genere la creazione di un'interfaccia utente in XAML e l'aggiunta di code-behind che opera sull'interfaccia utente. Man mano che le app vengono modificate e aumentano le dimensioni e l'ambito, possono verificarsi problemi di manutenzione complessi. Questi problemi includono l'accoppiamento stretto tra i controlli dell'interfaccia utente e la logica di business, che aumenta il costo di apportare modifiche all'interfaccia utente e la difficoltà di unit test di questo codice.
Il modello Model-View-ViewModel (MVVM) consente di separare in modo pulito la logica di business e presentazione di un'applicazione dall'interfaccia utente. La gestione di una separazione netta tra la logica dell'applicazione e l'interfaccia utente consente di risolvere numerosi problemi di sviluppo e di semplificare il test, la gestione e l'evoluzione di un'applicazione. Può anche migliorare notevolmente le opportunità di riutilizzo del codice e consente agli sviluppatori e ai progettisti dell'interfaccia utente di collaborare più facilmente durante lo sviluppo delle rispettive parti di un'app.
Modello MVVM
Esistono tre componenti principali nel modello MVVM: il modello, la visualizzazione e il modello di visualizzazione. Ognuno ha uno scopo distinto. La figura 2-1 mostra le relazioni tra i tre componenti.
Figura 2-1: Modello MVVM
Oltre a comprendere le responsabilità di ogni componente, è anche importante comprendere come interagiscono tra loro. A livello generale, la vista "conosce" il modello di visualizzazione e il modello di visualizzazione "conosce" il modello, ma il modello non è a conoscenza del modello di visualizzazione e il modello di visualizzazione non è a conoscenza della visualizzazione. Di conseguenza, il modello di visualizzazione isola la vista dal modello e consente al modello di evolversi indipendentemente dalla visualizzazione.
I vantaggi dell'uso del modello MVVM sono i seguenti:
- Se è presente un'implementazione del modello esistente che incapsula la logica di business esistente, può essere difficile o rischiosa modificarla. In questo scenario, il modello di visualizzazione funge da adattatore per le classi di modello e consente di evitare di apportare modifiche importanti al codice del modello.
- Gli sviluppatori possono creare unit test per il modello di visualizzazione e il modello, senza usare la vista. Gli unit test per il modello di visualizzazione possono esercitare esattamente la stessa funzionalità usata dalla visualizzazione.
- L'interfaccia utente dell'app può essere riprogettata senza toccare il codice, purché la visualizzazione sia implementata interamente in XAML. Pertanto, una nuova versione della vista dovrebbe funzionare con il modello di visualizzazione esistente.
- I progettisti e gli sviluppatori possono lavorare in modo indipendente e simultaneo sui relativi componenti durante il processo di sviluppo. I progettisti possono concentrarsi sulla visualizzazione, mentre gli sviluppatori possono lavorare sui componenti del modello di visualizzazione e del modello.
La chiave per usare MVVM consiste nel comprendere in modo efficace come considerare il codice dell'app nelle classi corrette e comprendere come interagiscono le classi. Le sezioni seguenti illustrano le responsabilità di ognuna delle classi nel modello MVVM.
Visualizza
La visualizzazione è responsabile della definizione della struttura, del layout e dell'aspetto di ciò che l'utente vede sullo schermo. Idealmente, ogni visualizzazione è definita in XAML, con un code-behind limitato che non contiene logica di business. In alcuni casi, tuttavia, il code-behind potrebbe contenere la logica dell'interfaccia utente che implementa il comportamento visivo difficile da esprimere in XAML, ad esempio le animazioni.
In un'applicazione, una Xamarin.Forms vista è in genere una Page
classe derivata o ContentView
derivata da . Tuttavia, le visualizzazioni possono anche essere rappresentate da un modello di dati, che specifica gli elementi dell'interfaccia utente da usare per rappresentare visivamente un oggetto quando viene visualizzato. Un modello di dati come vista non include code-behind ed è progettato per l'associazione a un tipo di modello di visualizzazione specifico.
Suggerimento
Evitare di abilitare e disabilitare gli elementi dell'interfaccia utente nel code-behind. Assicurarsi che i modelli di visualizzazione siano responsabili della definizione delle modifiche dello stato logico che influiscono su alcuni aspetti della visualizzazione della visualizzazione, ad esempio se un comando è disponibile o un'indicazione che un'operazione è in sospeso. Pertanto, abilitare e disabilitare gli elementi dell'interfaccia utente associandoli per visualizzare le proprietà del modello, anziché abilitarle e disabilitarle nel code-behind.
Sono disponibili diverse opzioni per l'esecuzione del codice nel modello di visualizzazione in risposta alle interazioni nella visualizzazione, ad esempio un clic di un pulsante o una selezione di elementi. Se un controllo supporta i comandi, la proprietà del Command
controllo può essere associata a dati a una ICommand
proprietà nel modello di visualizzazione. Quando viene richiamato il comando del controllo, verrà eseguito il codice nel modello di visualizzazione. Oltre ai comandi, i comportamenti possono essere collegati a un oggetto nella visualizzazione e possono restare in ascolto di un comando da richiamare o di un evento da richiamare. In risposta, il comportamento può quindi richiamare un oggetto ICommand
nel modello di visualizzazione o un metodo nel modello di visualizzazione.
Modello di visualizzazione
Il modello di visualizzazione implementa proprietà e comandi a cui la visualizzazione può associare i dati e notifica la visualizzazione di eventuali modifiche dello stato tramite eventi di notifica delle modifiche. Le proprietà e i comandi forniti dal modello di visualizzazione definiscono la funzionalità da offrire all'interfaccia utente, ma la visualizzazione determina la modalità di visualizzazione.
Suggerimento
Mantenere reattiva l'interfaccia utente con operazioni asincrone. Le app per dispositivi mobili devono mantenere sbloccato il thread dell'interfaccia utente per migliorare la percezione delle prestazioni dell'utente. Pertanto, nel modello di visualizzazione, usare metodi asincroni per le operazioni di I/O e generare eventi per notificare in modo asincrono le visualizzazioni delle modifiche alle proprietà.
Il modello di visualizzazione è anche responsabile del coordinamento delle interazioni della vista con tutte le classi di modello necessarie. In genere esiste una relazione uno-a-molti tra il modello di visualizzazione e le classi del modello. Il modello di visualizzazione potrebbe scegliere di esporre le classi del modello direttamente alla visualizzazione in modo che i controlli nella vista possano associarli direttamente a essi. In questo caso, le classi di modello dovranno essere progettate per supportare l'associazione dati e gli eventi di notifica delle modifiche.
Ogni modello di visualizzazione fornisce dati da un modello in un modulo che la vista può utilizzare facilmente. A tale scopo, il modello di visualizzazione a volte esegue la conversione dei dati. L'inserimento di questa conversione dei dati nel modello di visualizzazione è una buona idea perché fornisce proprietà a cui è possibile associare la vista. Ad esempio, il modello di visualizzazione potrebbe combinare i valori di due proprietà per semplificare la visualizzazione da parte della vista.
Suggerimento
Centralizzare le conversioni dei dati in un livello di conversione. È anche possibile usare convertitori come livello di conversione dei dati separato che si trova tra il modello di visualizzazione e la vista. Ciò può essere necessario, ad esempio, quando i dati richiedono una formattazione speciale che il modello di visualizzazione non fornisce.
Affinché il modello di visualizzazione partecipi al data binding bidirezionale con la vista, le relative proprietà devono generare l'evento PropertyChanged
. Visualizzare i modelli soddisfa questo requisito implementando l'interfaccia INotifyPropertyChanged
e generando l'evento PropertyChanged
quando viene modificata una proprietà.
Per le raccolte, viene fornita la visualizzazione.ObservableCollection<T>
Questa raccolta implementa la notifica di modifica della raccolta, in modo che lo sviluppatore non abbia la necessità di implementare l'interfaccia INotifyCollectionChanged
nelle raccolte.
Modello
Le classi del modello sono classi non visive che incapsulano i dati dell'app. Di conseguenza, il modello può essere considerato come rappresentare il modello di dominio dell'app, che in genere include un modello di dati insieme alla logica di business e convalida. Esempi di oggetti modello includono oggetti di trasferimento dei dati (DTO), oggetti CLR (POCO) e oggetti proxy e entità generati.
Le classi di modello vengono in genere usate insieme a servizi o repository che incapsulano l'accesso ai dati e la memorizzazione nella cache.
Connessione ing dei modelli di visualizzazione alle visualizzazioni
I modelli di visualizzazione possono essere connessi alle visualizzazioni usando le funzionalità di data binding di Xamarin.Forms. Esistono molti approcci che possono essere usati per costruire visualizzazioni e visualizzare modelli e associarli in fase di esecuzione. Questi approcci rientrano in due categorie, note come prima composizione della visualizzazione e prima composizione del modello di visualizzazione. La scelta tra la prima composizione della visualizzazione e la prima composizione del modello di visualizzazione è un problema di preferenza e complessità. Tuttavia, tutti gli approcci condividono lo stesso obiettivo, ovvero per la visualizzazione in modo che un modello di visualizzazione sia assegnato alla relativa proprietà BindingContext.
Con la prima composizione, l'app è concettualmente composta da visualizzazioni che si connettono ai modelli di visualizzazione da cui dipendono. Il vantaggio principale di questo approccio è che semplifica la creazione di app a accoppiamento libero e unit testabile perché i modelli di visualizzazione non hanno alcuna dipendenza dalle visualizzazioni stesse. È anche facile comprendere la struttura dell'app seguendo la relativa struttura visiva, invece di dover tenere traccia dell'esecuzione del codice per comprendere come vengono create e associate le classi. Inoltre, la prima costruzione della visualizzazione è allineata al sistema di navigazione responsabile della Xamarin.Forms costruzione di pagine quando si verifica lo spostamento, che rende un modello di visualizzazione la prima composizione complessa e non allineata alla piattaforma.
Con la prima composizione del modello di visualizzazione, l'app è costituita concettualmente da modelli di visualizzazione, con un servizio responsabile dell'individuazione della visualizzazione per un modello di visualizzazione. La prima composizione del modello di visualizzazione è più naturale per alcuni sviluppatori, poiché la creazione della visualizzazione può essere astratta, consentendo loro di concentrarsi sulla struttura logica non dell'interfaccia utente dell'app. Consente inoltre di creare modelli di visualizzazione da altri modelli di visualizzazione. Tuttavia, questo approccio è spesso complesso e può diventare difficile capire come vengono create e associate le varie parti dell'app.
Suggerimento
Mantenere indipendenti i modelli di visualizzazione e le visualizzazioni. L'associazione di viste a una proprietà in un'origine dati deve essere la dipendenza principale della vista dal modello di visualizzazione corrispondente. In particolare, non fare riferimento ai tipi di visualizzazione, ad esempio Button
e ListView
, dai modelli di visualizzazione. Seguendo i principi descritti qui, i modelli di visualizzazione possono essere testati in isolamento, riducendo quindi la probabilità di difetti software limitando l'ambito.
Le sezioni seguenti illustrano gli approcci principali per connettere i modelli di visualizzazione alle visualizzazioni.
Creazione di un modello di visualizzazione in modo dichiarativo
L'approccio più semplice consiste nell'creare un'istanza dichiarativa del modello di visualizzazione corrispondente in XAML. Quando la vista viene costruita, verrà costruito anche l'oggetto modello di visualizzazione corrispondente. Questo approccio è illustrato nell'esempio di codice seguente:
<ContentPage ... xmlns:local="clr-namespace:eShop">
<ContentPage.BindingContext>
<local:LoginViewModel />
</ContentPage.BindingContext>
...
</ContentPage>
Quando viene creato , un'istanza ContentPage
di LoginViewModel
viene creata automaticamente e impostata come .BindingContext
Questa costruzione dichiarativa e l'assegnazione del modello di visualizzazione dalla vista presenta il vantaggio che è semplice, ma presenta lo svantaggio che richiede un costruttore predefinito (senza parametri) nel modello di visualizzazione.
Creazione di un modello di visualizzazione a livello di codice
Una vista può avere codice nel file code-behind che comporta l'assegnazione del modello di visualizzazione alla relativa BindingContext
proprietà. Questa operazione viene spesso eseguita nel costruttore della visualizzazione, come illustrato nell'esempio di codice seguente:
public LoginView()
{
InitializeComponent();
BindingContext = new LoginViewModel(navigationService);
}
La costruzione e l'assegnazione a livello di codice del modello di visualizzazione all'interno del code-behind della visualizzazione hanno il vantaggio che è semplice. Tuttavia, lo svantaggio principale di questo approccio è che la vista deve fornire al modello di visualizzazione tutte le dipendenze necessarie. L'uso di un contenitore di inserimento delle dipendenze consente di mantenere l'accoppiamento libero tra la visualizzazione e il modello di visualizzazione. Per altre informazioni, vedere Inserimento delle dipendenze.
Creazione di una vista definita come modello di dati
Una vista può essere definita come modello di dati e associata a un tipo di modello di visualizzazione. I modelli di dati possono essere definiti come risorse oppure possono essere definiti inline all'interno del controllo che visualizzerà il modello di visualizzazione. Il contenuto del controllo è l'istanza del modello di visualizzazione e il modello di dati viene usato per rappresentarlo visivamente. Questa tecnica è un esempio di una situazione in cui viene creata un'istanza del modello di visualizzazione, seguita dalla creazione della visualizzazione.
Creazione automatica di un modello di visualizzazione con un localizzatore di modelli di visualizzazione
Un localizzatore di modelli di visualizzazione è una classe personalizzata che gestisce la creazione di istanze dei modelli di visualizzazione e la relativa associazione alle visualizzazioni. Nell'app per dispositivi mobili eShopOnContainers la ViewModelLocator
classe ha una proprietà associata, AutoWireViewModel
, usata per associare i modelli di visualizzazione alle visualizzazioni. Nel codice XAML della visualizzazione questa proprietà associata è impostata su true per indicare che il modello di visualizzazione deve essere connesso automaticamente alla visualizzazione, come illustrato nell'esempio di codice seguente:
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
La AutoWireViewModel
proprietà è una proprietà associabile inizializzata su false e quando viene chiamato il relativo valore viene chiamato il OnAutoWireViewModelChanged
gestore eventi. Questo metodo risolve il modello di visualizzazione per la visualizzazione. L'esempio di codice seguente mostra come viene ottenuto questo risultato:
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
if (view == null)
{
return;
}
var viewType = view.GetType();
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = string.Format(
CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null)
{
return;
}
var viewModel = _container.Resolve(viewModelType);
view.BindingContext = viewModel;
}
Il OnAutoWireViewModelChanged
metodo tenta di risolvere il modello di visualizzazione usando un approccio basato su convenzioni. Questa convenzione presuppone che:
- I modelli di visualizzazione si trovano nello stesso assembly dei tipi di visualizzazione.
- Le visualizzazioni si trovano in un oggetto . Visualizza lo spazio dei nomi figlio.
- I modelli di visualizzazione si trovano in un oggetto . Spazio dei nomi figlio ViewModels.
- I nomi dei modelli di visualizzazione corrispondono ai nomi di visualizzazione e terminano con "ViewModel".
Infine, il OnAutoWireViewModelChanged
metodo imposta l'oggetto BindingContext
del tipo di visualizzazione sul tipo di modello di visualizzazione risolto. Per altre informazioni sulla risoluzione del tipo di modello di visualizzazione, vedere Risoluzione.
Questo approccio ha il vantaggio che un'app ha una singola classe responsabile della creazione di istanze dei modelli di visualizzazione e della relativa connessione alle visualizzazioni.
Suggerimento
Usare un localizzatore di modelli di visualizzazione per semplificare la sostituzione. Un localizzatore di modelli di visualizzazione può essere usato anche come punto di sostituzione per implementazioni alternative di dipendenze, ad esempio per unit test o dati in fase di progettazione.
Aggiornamento delle visualizzazioni in risposta alle modifiche nel modello di visualizzazione o nel modello di visualizzazione sottostante
Tutte le classi di modello e modello di visualizzazione accessibili a una vista devono implementare l'interfaccia INotifyPropertyChanged
. L'implementazione di questa interfaccia in un modello di visualizzazione o in una classe modello consente alla classe di fornire notifiche di modifica a tutti i controlli associati a dati nella visualizzazione quando il valore della proprietà sottostante cambia.
Le app devono essere strutturate per l'uso corretto della notifica delle modifiche alle proprietà, soddisfacendo i requisiti seguenti:
- Genera sempre un
PropertyChanged
evento se il valore di una proprietà pubblica cambia. Non presupporre che la generazione dell'evento possa essere ignorata a causa della conoscenza del modo in cui si verifica l'associazionePropertyChanged
XAML. - Generazione sempre di un
PropertyChanged
evento per tutte le proprietà calcolate i cui valori vengono utilizzati da altre proprietà nel modello di visualizzazione o nel modello. - Generazione sempre dell'evento
PropertyChanged
alla fine del metodo che apporta una modifica di proprietà o quando l'oggetto è noto come in uno stato sicuro. La generazione dell'evento interrompe l'operazione richiamando in modo sincrono i gestori dell'evento. Se ciò si verifica durante un'operazione, potrebbe esporre l'oggetto alle funzioni di callback quando si trova in uno stato non sicuro e parzialmente aggiornato. Inoltre, è possibile che le modifiche a catena vengano attivate dagliPropertyChanged
eventi. Le modifiche a catena richiedono in genere il completamento degli aggiornamenti prima che la modifica a catena sia sicura da eseguire. - Non generare mai un
PropertyChanged
evento se la proprietà non viene modificata. Ciò significa che è necessario confrontare i valori precedenti e nuovi prima di generare l'eventoPropertyChanged
. - Non generare mai l'evento durante il
PropertyChanged
costruttore di un modello di visualizzazione se si inizializza una proprietà. I controlli associati a dati nella visualizzazione non saranno sottoscritti per ricevere notifiche di modifica a questo punto. - Non generare mai più di un
PropertyChanged
evento con lo stesso argomento del nome di proprietà all'interno di una singola chiamata sincrona di un metodo pubblico di una classe. Ad esempio, data unaNumberOfItems
proprietà il cui archivio di backup è il_numberOfItems
campo , se un metodo incrementa_numberOfItems
cinquanta volte durante l'esecuzione di un ciclo, deve generare una notifica di modifica dellaNumberOfItems
proprietà solo una volta, dopo il completamento di tutto il lavoro. Per i metodi asincroni, generare l'eventoPropertyChanged
per un determinato nome di proprietà in ogni segmento sincrono di una catena di continuazione asincrona.
L'app per dispositivi mobili eShopOnContainers usa la ExtendedBindableObject
classe per fornire notifiche di modifica, come illustrato nell'esempio di codice seguente:
public abstract class ExtendedBindableObject : BindableObject
{
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
var name = GetMemberInfo(property).Name;
OnPropertyChanged(name);
}
private MemberInfo GetMemberInfo(Expression expression)
{
...
}
}
La classe di BindableObject
Xamarin.Form implementa l'interfaccia INotifyPropertyChanged
e fornisce un OnPropertyChanged
metodo. La ExtendedBindableObject
classe fornisce il RaisePropertyChanged
metodo per richiamare la notifica di modifica delle proprietà e in questo modo usa la funzionalità fornita dalla BindableObject
classe .
Ogni classe del modello di visualizzazione nell'app per dispositivi mobili eShopOnContainers deriva dalla ViewModelBase
classe , che a sua volta deriva dalla ExtendedBindableObject
classe . Pertanto, ogni classe del modello di visualizzazione usa il RaisePropertyChanged
metodo nella ExtendedBindableObject
classe per fornire la notifica delle modifiche delle proprietà. L'esempio di codice seguente mostra come l'app per dispositivi mobili eShopOnContainers richiama la notifica delle modifiche delle proprietà usando un'espressione lambda:
public bool IsLogin
{
get
{
return _isLogin;
}
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
Si noti che l'uso di un'espressione lambda in questo modo comporta un costo di prestazioni ridotto perché l'espressione lambda deve essere valutata per ogni chiamata. Anche se il costo delle prestazioni è ridotto e normalmente non influisce su un'app, i costi possono accumularsi quando sono presenti molte notifiche di modifica. Tuttavia, il vantaggio di questo approccio è che offre supporto per la sicurezza dei tipi in fase di compilazione e il refactoring durante la ridenominazione delle proprietà.
Interazione dell'interfaccia utente con comandi e comportamenti
Nelle app per dispositivi mobili, le azioni vengono in genere richiamate in risposta a un'azione dell'utente, ad esempio un clic sul pulsante, che può essere implementata creando un gestore eventi nel file code-behind. Tuttavia, nel modello MVVM, la responsabilità dell'implementazione dell'azione è il modello di visualizzazione e l'inserimento del codice nel code-behind deve essere evitato.
I comandi offrono un modo pratico per rappresentare le azioni che possono essere associate ai controlli nell'interfaccia utente. Incapsulano il codice che implementa l'azione e consentono di mantenerlo separato dalla relativa rappresentazione visiva nella visualizzazione. Xamarin.Forms include controlli che possono essere connessi in modo dichiarativo a un comando e questi controlli richiamano il comando quando l'utente interagisce con il controllo.
I comportamenti consentono anche la connessione dichiarativa dei controlli a un comando. Tuttavia, i comportamenti possono essere usati per richiamare un'azione associata a un intervallo di eventi generati da un controllo . Di conseguenza, i comportamenti rispondono a molti degli stessi scenari dei controlli abilitati ai comandi, offrendo al tempo stesso un maggiore grado di flessibilità e controllo. Inoltre, i comportamenti possono essere usati anche per associare oggetti comando o metodi ai controlli non progettati specificamente per interagire con i comandi.
Implementazione dei comandi
I modelli di visualizzazione espongono in genere le proprietà dei comandi, per l'associazione dalla vista, ovvero istanze di oggetti che implementano l'interfaccia ICommand
. Un certo numero di Xamarin.Forms controlli fornisce una Command
proprietà, che può essere associata a un ICommand
oggetto fornito dal modello di visualizzazione. L'interfaccia ICommand
definisce un Execute
metodo, che incapsula l'operazione stessa, un CanExecute
metodo, che indica se il comando può essere richiamato e un CanExecuteChanged
evento che si verifica quando si verificano modifiche che influiscono sull'esecuzione del comando. Le Command
classi e Command<T>
, fornite da Xamarin.Forms, implementano l'interfaccia ICommand
, dove T
è il tipo degli argomenti in Execute
e CanExecute
.
All'interno di un modello di visualizzazione deve essere presente un oggetto di tipo Command
o Command<T>
per ogni proprietà pubblica nel modello di visualizzazione di tipo ICommand
. Il Command
costruttore o Command<T>
richiede un Action
oggetto callback chiamato quando viene richiamato il ICommand.Execute
metodo . Il CanExecute
metodo è un parametro del costruttore facoltativo ed è un Func
oggetto che restituisce un oggetto bool
.
Il codice seguente illustra come viene costruita un'istanza Command
di , che rappresenta un comando register, specificando un delegato al metodo del modello di Register
visualizzazione:
public ICommand RegisterCommand => new Command(Register);
Il comando viene esposto alla visualizzazione tramite una proprietà che restituisce un riferimento a un oggetto ICommand
. Quando il Execute
metodo viene chiamato sull'oggetto Command
, inoltra semplicemente la chiamata al metodo nel modello di visualizzazione tramite il delegato specificato nel Command
costruttore.
Un metodo asincrono può essere richiamato da un comando usando le async
parole chiave e await
quando si specifica il delegato del Execute
comando. Ciò indica che il callback è un Task
oggetto e deve essere atteso. Ad esempio, il codice seguente illustra come Command
un'istanza, che rappresenta un comando di accesso, viene costruita specificando un delegato al metodo del SignInAsync
modello di visualizzazione:
public ICommand SignInCommand => new Command(async () => await SignInAsync());
I parametri possono essere passati alle Execute
azioni e CanExecute
usando la Command<T>
classe per creare un'istanza del comando. Ad esempio, il codice seguente mostra come viene usata un'istanza Command<T>
per indicare che il NavigateAsync
metodo richiederà un argomento di tipo string
:
public ICommand NavigateCommand => new Command<string>(NavigateAsync);
In entrambe le Command
classi e Command<T>
il delegato al CanExecute
metodo in ogni costruttore è facoltativo. Se non viene specificato un delegato, verrà restituito Command
true
per CanExecute
. Tuttavia, il modello di visualizzazione può indicare una modifica dello stato del CanExecute
comando chiamando il ChangeCanExecute
metodo sull'oggetto Command
. In questo modo l'evento CanExecuteChanged
viene generato. Tutti i controlli nell'interfaccia utente associati al comando aggiorneranno quindi lo stato abilitato in modo da riflettere la disponibilità del comando associato a dati.
Richiamo di comandi da una visualizzazione
Nell'esempio di codice seguente viene illustrato come un oggetto Grid
nell'oggetto viene associato a RegisterCommand
nella LoginViewModel
classe usando un'istanza LoginView
TapGestureRecognizer
di :
<Grid Grid.Column="1" HorizontalOptions="Center">
<Label Text="REGISTER" TextColor="Gray"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
Un parametro di comando può anche essere definito facoltativamente usando la CommandParameter
proprietà . Il tipo dell'argomento previsto viene specificato nei Execute
metodi di destinazione e CanExecute
. TapGestureRecognizer
Richiama automaticamente il comando di destinazione quando l'utente interagisce con il controllo associato. Il parametro di comando, se specificato, verrà passato come argomento al delegato del Execute
comando.
Implementazione di comportamenti
I comportamenti consentono di aggiungere funzionalità ai controlli dell'interfaccia utente senza doverli sottoclassare. La funzionalità viene invece implementata in una classe di comportamento e associata al controllo come se fosse parte del controllo stesso. I comportamenti consentono di implementare il codice che normalmente è necessario scrivere come code-behind, perché interagisce direttamente con l'API del controllo, in modo che possa essere concisamente collegato al controllo e inserito in un pacchetto per il riutilizzo in più visualizzazioni o app. Nel contesto di MVVM, i comportamenti sono un approccio utile per la connessione dei controlli ai comandi.
Un comportamento associato a un controllo tramite proprietà associate è noto come comportamento associato. Il comportamento può quindi usare l'API esposta dell'elemento a cui è collegata per aggiungere funzionalità a tale controllo o ad altri controlli nella struttura ad albero visuale della visualizzazione. L'app per dispositivi mobili eShopOnContainers contiene la LineColorBehavior
classe , che è un comportamento associato. Per altre informazioni su questo comportamento, vedere Visualizzazione degli errori di convalida.
Un Xamarin.Forms comportamento è una classe che deriva dalla Behavior
classe o Behavior<T>
, dove T
è il tipo del controllo a cui deve essere applicato il comportamento. Queste classi forniscono OnAttachedTo
metodi e OnDetachingFrom
, che devono essere sottoposti a override per fornire la logica che verrà eseguita quando il comportamento è associato e scollegato dai controlli.
Nell'app per dispositivi mobili eShopOnContainers la BindableBehavior<T>
classe deriva dalla Behavior<T>
classe . Lo scopo della BindableBehavior<T>
classe è fornire una classe di base per Xamarin.Forms i comportamenti che richiedono l'impostazione BindingContext
del comportamento sul controllo associato.
La BindableBehavior<T>
classe fornisce un metodo sostituibile OnAttachedTo
che imposta l'oggetto BindingContext
del comportamento e un metodo sostituibile OnDetachingFrom
che pulisce l'oggetto BindingContext
. La classe archivia anche un riferimento al controllo associato nella proprietà AssociatedObject
.
L'app per dispositivi mobili eShopOnContainers include una EventToCommandBehavior
classe che esegue un comando in risposta a un evento che si verifica. Questa classe deriva dalla BindableBehavior<T>
classe in modo che il comportamento possa essere associato ed eseguito da ICommand
una Command
proprietà specificata quando viene utilizzato il comportamento. L'esempio di codice seguente visualizza la classe EventToCommandBehavior
:
public class EventToCommandBehavior : BindableBehavior<View>
{
...
protected override void OnAttachedTo(View visualElement)
{
base.OnAttachedTo(visualElement);
var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();
if (events.Any())
{
_eventInfo = events.FirstOrDefault(e => e.Name == EventName);
if (_eventInfo == null)
throw new ArgumentException(string.Format(
"EventToCommand: Can't find any event named '{0}' on attached type",
EventName));
AddEventHandler(_eventInfo, AssociatedObject, OnFired);
}
}
protected override void OnDetachingFrom(View view)
{
if (_handler != null)
_eventInfo.RemoveEventHandler(AssociatedObject, _handler);
base.OnDetachingFrom(view);
}
private void AddEventHandler(
EventInfo eventInfo, object item, Action<object, EventArgs> action)
{
...
}
private void OnFired(object sender, EventArgs eventArgs)
{
...
}
}
I OnAttachedTo
metodi e OnDetachingFrom
vengono usati per registrare e annullare la registrazione di un gestore eventi per l'evento definito nella EventName
proprietà . Quindi, quando viene generato l'evento, viene richiamato il OnFired
metodo , che esegue il comando .
Il vantaggio dell'uso EventToCommandBehavior
di per eseguire un comando quando viene generato un evento è che i comandi possono essere associati ai controlli che non sono stati progettati per interagire con i comandi. In questo modo, inoltre, il codice di gestione degli eventi viene spostato in modo da visualizzare i modelli, in cui può essere sottoposto a unit test.
Richiamo di comportamenti da una visualizzazione
EventToCommandBehavior
è particolarmente utile per associare un comando a un controllo che non supporta i comandi. Ad esempio, ProfileView
usa EventToCommandBehavior
per eseguire OrderDetailCommand
l'oggetto quando viene generato l'evento ItemTapped
in ListView
che elenca gli ordini dell'utente, come illustrato nel codice seguente:
<ListView>
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding OrderDetailCommand}"
EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />
</ListView.Behaviors>
...
</ListView>
In fase di esecuzione, EventToCommandBehavior
risponderà all'interazione con ListView
. Quando un elemento viene selezionato in ListView
, verrà generato l'evento ItemTapped
, che eseguirà OrderDetailCommand
in ProfileViewModel
. Per impostazione predefinita, gli argomenti dell'evento vengono passati al comando. Questi dati vengono convertiti mentre vengono passati tra l'origine e la destinazione dal convertitore specificato nella EventArgsConverter
proprietà , che restituisce l'oggetto Item
dell'oggetto ListView
ItemTappedEventArgs
da . Pertanto, quando viene eseguito , l'oggetto OrderDetailCommand
selezionato Order
viene passato come parametro all'azione registrata.
Per altre informazioni sui comportamenti, vedere Comportamenti.
Riepilogo
Il modello Model-View-ViewModel (MVVM) consente di separare in modo pulito la logica di business e presentazione di un'applicazione dall'interfaccia utente. La gestione di una separazione netta tra la logica dell'applicazione e l'interfaccia utente consente di risolvere numerosi problemi di sviluppo e di semplificare il test, la gestione e l'evoluzione di un'applicazione. Può anche migliorare notevolmente le opportunità di riutilizzo del codice e consente agli sviluppatori e ai progettisti dell'interfaccia utente di collaborare più facilmente durante lo sviluppo delle rispettive parti di un'app.
Usando il modello MVVM, l'interfaccia utente dell'app e la presentazione e la logica di business sottostanti sono separate in tre classi separate: la visualizzazione, che incapsula l'interfaccia utente e la logica dell'interfaccia utente; il modello di visualizzazione, che incapsula la logica di presentazione e lo stato; e il modello, che incapsula la logica di business e i dati dell'app.