Condividi tramite


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.

The MVVM pattern

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 Pageclasse derivata o ContentViewderivata 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'associazione PropertyChanged 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 dagli PropertyChanged 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'evento PropertyChanged .
  • 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 una NumberOfItems 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 della NumberOfItems proprietà solo una volta, dopo il completamento di tutto il lavoro. Per i metodi asincroni, generare l'evento PropertyChanged 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 Commandtrue 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 LoginViewTapGestureRecognizer 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 ListViewItemTappedEventArgsda . 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.