Condividi tramite


Model-View-ViewModel (MVVM)

Suggerimento

Questo contenuto è un estratto dell'eBook, Enterprise Application Patterns Using .NETMAUI, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

L'esperienza per gli sviluppatori .NET MAUI comporta in genere la creazione di un'interfaccia utente in XAML e l'aggiunta di code-behind che opera sull'interfaccia utente. I problemi di manutenzione complessi possono verificarsi man mano che le app vengono modificate e aumentano in dimensioni e ambito. 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 MVVM consente di separare in modo pulito la logica di business e presentazione di un'applicazione dalla relativa 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 semplifica il test, la gestione e l'evoluzione di un'applicazione. Può anche migliorare significativamente 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.

Il modello MVVM

Esistono tre componenti principali nel modello MVVM: il modello, la visualizzazione e il modello di visualizzazione. Ognuno ha uno scopo distinto. Il diagramma seguente mostra le relazioni tra i tre componenti.

The MVVM pattern

Oltre a comprendere le responsabilità di ogni componente, è anche importante comprendere come interagiscono. 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 visualizzazione dal modello e consente al modello di evolversi indipendentemente dalla visualizzazione.

I vantaggi dell'uso del modello MVVM sono i seguenti:

  • Se un'implementazione del modello esistente 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 del modello e impedisce di apportare modifiche importanti al codice del modello.
  • Gli sviluppatori possono creare unit test per il modello di visualizzazione e per il modello, senza usare la visualizzazione. 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 modello di visualizzazione e il codice del modello, purché la visualizzazione sia implementata interamente in XAML o C#. Pertanto, una nuova versione della visualizzazione dovrebbe funzionare con il modello di visualizzazione esistente.
  • I progettisti e gli sviluppatori possono lavorare in modo indipendente e simultaneo sui relativi componenti durante lo 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 in modo efficace consiste nel comprendere come suddividere il codice dell'app nelle classi corrette e come le classi interagiscono tra loro. 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 un comportamento visivo difficile da esprimere in XAML, ad esempio nelle animazioni.

In un'applicazione .NET MAUI una visualizzazione è in genere una classe derivata da ContentPage o da ContentView. 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 una visualizzazione 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, come ad esempio la disponibilità di un comando o l'indicazione che un'operazione è in attesa. 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à Command del controllo può essere associata a una proprietà ICommand 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 associati a un oggetto nella visualizzazione e possono restare in ascolto di un comando da richiamare o dell'evento da sollevare. 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 multipiattaforma 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 visualizzazione possano associare i dati direttamente a esse. In questo caso, le classi di modello dovranno essere progettate per supportare l'associazione dei dati e gli eventi di notifica delle modifiche.

Ogni modello di visualizzazione fornisce dati da un modello in un modulo che la visualizzazione 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 visualizzazione. Ad esempio, il modello di visualizzazione può combinare i valori di due proprietà per semplificare la visualizzazione da parte della visualizzazione.

Suggerimento

Centralizzare le conversioni dei dati in un livello di conversione.

È anche possibile usare dei convertitori come livello di conversione dei dati separato che si trova tra il modello di visualizzazione e la visualizzazione. 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 all’associazione di dati 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 fornito ObservableCollection<T> di facile visualizzazione. 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, si può pensare che il modello rappresenti il modello di dominio dell'app, che di solito include un modello di dati insieme alla logica di business e di 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 dei modelli di visualizzazione alle visualizzazioni

I modelli di visualizzazione possono essere connessi alle visualizzazioni usando le funzionalità di data binding di .NET MAUI. 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 che la visualizzazione abbia un modello di visualizzazione assegnato alla relativa proprietà BindingContext.

Con la composizione view first, 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 libere da accoppiamenti e testabili dall'unità, 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 spostamento di Microsoft Maui responsabile della costruzione di pagine quando si verifica la navigazione, che rende un modello di visualizzazione la prima composizione complessa e non allineata con la 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 comprendere come vengono create e associate le varie parti dell'app.

Suggerimento

Mantenere indipendenti i modelli di visualizzazione e le visualizzazioni.

L'associazione di visualizzazioni 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 nel creare un'istanza dichiarativa del modello di visualizzazione corrispondente in XAML. Quando la visualizzazione 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>
    <!-- Omitted for brevity... -->
</ContentPage>

Quando viene creato ContentPage, viene creata automaticamente un'istanza di LoginViewModel e impostata come BindingContext della vista.

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, con conseguente assegnazione del modello di visualizzazione alla relativa proprietà BindingContext. 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 di essere semplici. Tuttavia, lo svantaggio principale di questo approccio è che la visualizzazione 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 Dependency injection (Inserimento delle dipendenze).

Aggiornamento delle visualizzazioni in risposta alle modifiche apportate al modello di visualizzazione o al 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.

L'app deve essere progettata per l'uso corretto della notifica di modifica delle proprietà, soddisfacendo i seguenti requisiti:

  • Generare sempre un evento PropertyChanged se il valore di una proprietà pubblica cambia. Non presupporre che la generazione dell'evento PropertyChanged possa essere ignorata a causa della conoscenza del modo in cui si verifica l'associazione XAML.
  • Generare sempre un evento PropertyChanged per qualsiasi proprietà calcolata i cui valori vengono utilizzati da altre proprietà nel modello di visualizzazione o nel modello di visualizzazione.
  • Generare sempre l'evento PropertyChanged alla fine del metodo che apporta una modifica di proprietà o quando si sa che l'oggetto è in uno stato sicuro. Generare l'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 eventi PropertyChanged. 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 evento PropertyChanged 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 PropertyChanged durante il 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 evento PropertyChanged 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 proprietà NumberOfItems il cui archivio di backup è il campo _numberOfItems, se un metodo incrementa _numberOfItems cinquanta volte durante l'esecuzione di un ciclo, deve generare una notifica di modifica delle proprietà alla proprietà NumberOfItems 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.

Un modo semplice per fornire questa funzionalità consiste nel creare un'estensione della classe BindableObject. In questo esempio, la classe ExtendedBindableObject fornisce 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)
    {
        // Omitted for brevity ...
    }
}

La classe BindableObject di MAUI .NET implementa l'interfaccia INotifyPropertyChanged e fornisce un metodo OnPropertyChanged. La classe ExtendedBindableObject fornisce il metodo RaisePropertyChanged per richiamare la notifica delle modifiche delle proprietà e in questo modo usa la funzionalità fornita dalla classe BindableObject.

Le classi del modello di visualizzazione possono quindi derivare dalla classe ExtendedBindableObject. Pertanto, ogni classe del modello di visualizzazione usa il metodo RaisePropertyChanged nella classe ExtendedBindableObject per fornire la notifica delle modifiche delle proprietà. L'esempio di codice seguente illustra come l'app multipiattaforma eShopOnContainers richiama la notifica delle modifiche delle proprietà usando un'espressione lambda:

public bool IsLogin
{
    get => _isLogin;
    set
    {
        _isLogin = value;
        RaisePropertyChanged(() => IsLogin);
    }
}

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 in genere 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à.

Framework MVVM

Il modello MVVM è ben definito in .NET e la community ha creato molti framework che semplificano questo sviluppo. Ogni framework fornisce un set diverso di funzionalità, ma è standard che forniscano un modello di visualizzazione comune con un'implementazione dell'interfaccia INotifyPropertyChanged. Le funzionalità aggiuntive dei framework MVVM includono comandi personalizzati, helper di navigazione, componenti di inserimento delle dipendenze/localizzatore di servizi e integrazione della piattaforma dell'interfaccia utente. Anche se non è necessario usare questi framework, possono velocizzare e standardizzare lo sviluppo. L'app multipiattaforma eShopOnContainers usa .NET Community MVVM Toolkit. Quando si sceglie un framework, è consigliabile considerare le esigenze dell'applicazione e i punti di forza del team. L'elenco seguente include alcuni dei framework MVVM più comuni per MAUI .NET.

Interazione dell'interfaccia utente con comandi e comportamenti

Nelle app multipiattaforma, le azioni vengono in genere richiamate in risposta a un'azione dell'utente, ad esempio un clic su un 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. In questo modo, i modelli di visualizzazione diventano più portabili in nuove piattaforme, in quanto non hanno una dipendenza diretta dagli eventi forniti dal framework dell'interfaccia utente della piattaforma. MAUI .NET 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 in genere espongono proprietà pubbliche, per l'associazione dalla visualizzazione, che implementano l'interfaccia ICommand. Molti controlli e movimenti di MAUI .NET forniscono una proprietà Command, che può essere associata a un oggetto ICommand fornito dal modello di visualizzazione. Il controllo pulsante è uno dei controlli più usati, fornendo una proprietà di comando che viene eseguita quando si fa clic sul pulsante.

Nota

Sebbene sia possibile esporre l'implementazione effettiva dell'interfaccia ICommand usata dal modello di visualizzazione (ad esempio, Command<T> o RelayCommand), è consigliabile esporre pubblicamente i comandi come ICommand. In questo modo, se è necessario modificare l'implementazione in un secondo momento, può essere facilmente scambiata.

L'interfaccia ICommand definisce un metodo Execute, che incapsula l'operazione stessa, un metodo CanExecute, che indica se il comando può essere richiamato e un evento CanExecuteChanged che si verifica quando si verificano modifiche che influiscono sull'esecuzione del comando. Nella maggior parte dei casi, verrà fornito solo il metodo Execute per i comandi. Per una panoramica più dettagliata di ICommand, vedere la documentazione relativa ai comandi per MAUI .NET.

Con MAUI .NET vengono fornite le classi Command e Command<T> che implementano l'interfaccia ICommand, dove T è il tipo degli argomenti da Execute e CanExecute. Command e Command<T> sono implementazioni di base che forniscono il set minimo di funzionalità necessarie per l'interfaccia ICommand.

Nota

Molti framework MVVM offrono implementazioni più avanzate di funzionalità dell'interfaccia ICommand.

Il costruttore Command o Command<T> richiede un oggetto callback Action chiamato quando viene richiamato il metodo ICommand.Execute. Il metodo CanExecute è un parametro del costruttore facoltativo ed è un Func che restituisce un valore bool.

L'app multipiattaforma eShopOnContainers usa il RelayCommand e AsyncRelayCommand. Il vantaggio principale per le applicazioni moderne è che AsyncRelayCommand offre funzionalità migliori per le operazioni asincrone.

Il codice seguente illustra come viene costruita un'istanza di Command, che rappresenta un comando register, specificando un delegato al metodo del modello di visualizzazione Register:

public ICommand RegisterCommand { get; }

Il comando viene esposto alla visualizzazione tramite una proprietà che restituisce un riferimento a ICommand. Quando il metodo Execute viene chiamato sull'oggetto Command, inoltra semplicemente la chiamata al metodo nel modello di visualizzazione tramite il delegato specificato nel costruttore Command. Un metodo asincrono può essere richiamato da un comando usando le parole chiave async e await quando si specifica il delegato Execute del comando. Ciò indica che il callback è un oggetto Task e deve essere atteso. Ad esempio, il codice seguente illustra come un'istanza di ICommand, che rappresenta un comando di accesso, viene costruita specificando un delegato al metodo del modello di visualizzazione SignInAsync:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

I parametri possono essere passati alle azioni Execute e CanExecute usando la classe AsyncRelayCommand<T> per creare un'istanza del comando. Ad esempio, il codice seguente mostra come viene usata un'istanza di AsyncRelayCommand<T> per indicare che il metodo NavigateAsync richiederà un argomento di tipo string:

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

Sia nelle classi RelayCommand che nelle classi RelayCommand<T>, il delegato al metodo CanExecute in ogni costruttore è facoltativo. Se non viene specificato un delegato, Command restituirà true per CanExecute. Tuttavia, il modello di visualizzazione può indicare una modifica dello stato di CanExecute del comando chiamando il metodo ChangeCanExecute sull'oggetto Command. In questo modo l'evento CanExecuteChanged viene generato. Tutti i controlli dell'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 Grid in LoginView viene associato a RegisterCommand nella classe LoginViewModel usando un'istanza di TapGestureRecognizer:

<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 proprietà CommandParameter. Il tipo dell'argomento previsto viene specificato nei metodi di destinazione Execute e CanExecute. TapGestureRecognizer richiama automaticamente il comando di destinazione quando l'utente interagisce con il controllo associato. L'oggetto CommandParameter, se specificato, verrà passato come argomento al delegato Execute del 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 in genere è 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.

Un comportamento di MAUI .NET è una classe che deriva dalla classe Behavior o Behavior<T>, dove T è il tipo del controllo a cui applicare il comportamento. Queste classi forniscono metodi OnAttachedTo e OnDetachingFrom, che devono essere sottoposti a override per fornire la logica che verrà eseguita quando il comportamento viene collegato ai controlli e scollegato.

Nell'app multipiattaforma eShopOnContainers la classe BindableBehavior<T> deriva dalla classe Behavior<T>. Lo scopo della classe BindableBehavior<T> è fornire una classe di base per i comportamenti di MAUI .NET che richiedono BindingContext del comportamento da impostare sul controllo associato.

La classe BindableBehavior<T> fornisce un metodo di OnAttachedTo sostituibile che imposta il BindingContext del comportamento e un metodo di OnDetachingFrom sostituibile che pulisce il BindingContext.

L'app multipiattaforma eShopOnContainers include una classe EventToCommandBehavior fornita dal toolkit Community MAUI. EventToCommandBehavior esegue un comando in risposta a un evento che si verifica. Questa classe deriva dalla classe BaseBehavior<View> in modo che il comportamento possa essere associato a ed eseguire un ICommand specificato da una proprietà Command quando viene utilizzato il comportamento. L'esempio di codice seguente visualizza la classe EventToCommandBehavior:

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

I metodi OnAttachedTo e OnDetachingFrom vengono usati per registrare e annullare la registrazione di un gestore eventi per l'evento definito nella proprietà EventName. Quindi, quando viene generato l'evento, viene richiamato il metodo OnTriggerHandled, che esegue il comando.

Il vantaggio di usare EventToCommandBehavior per eseguire un comando quando viene generato un evento è che i comandi possono essere associati a controlli non progettati per interagire con i comandi. In questo modo, inoltre, il codice di gestione degli eventi viene spostato in modo da visualizzare i modelli, dove 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, LoginView usa EventToCommandBehavior per eseguire ValidateCommand quando l'utente modifica il valore della password, come illustrato nel codice seguente:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

In fase di esecuzione, EventToCommandBehavior risponderà all'interazione con Entry. Quando un utente digita nel campo Entry, verrà generato l'evento TextChanged, che eseguirà ValidateCommand in LoginViewModel. Per impostazione predefinita, gli argomenti dell'evento vengono passati al comando. Se necessario, la proprietà EventArgsConverter può essere usata per convertire il EventArgs fornito dall'evento in un valore previsto dal comando come input.

Per altre informazioni sui comportamenti, vedere Comportamenti nel Centro per sviluppatori MAUI .NET.

Riepilogo

Il modello Model-View-ViewModel (MVVM) consente di separare in modo pulito la logica di business e presentazione di un'applicazione dalla relativa 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 semplifica il test, la gestione e l'evoluzione di un'applicazione. Può anche migliorare significativamente 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.