Usare i comandi in un modello di visualizzazione

Completato

Si è visto come ottenere dati dai modelli di visualizzazione all'interfaccia utente e come usare il binding bidirezionale per recuperare i dati nei modelli di visualizzazione.

L'uso delle associazioni bidirezionali è il modo più adatto di reagire alle modifiche provenienti dall'interfaccia utente ogni volta che i dati cambiano. Molti aspetti che verrebbero gestiti come eventi possono essere gestiti usando associazioni bidirezionali e lo schema Model-View-ViewModel (MVVM). Altri esempi sono elementi come Switch.IsToggled e Slider.Value, che possono essere riflessi nel modello di visualizzazione come valore booleano o intero, senza dover usare eventi.

Ma esistono alcuni aspetti come l'attivazione di un elemento Button o MenuItem che non sono direttamente collegati ai dati che cambiano. Queste interazioni richiedono comunque una gestione simile a quella degli eventi. Poiché questi componenti dell'interfaccia utente in genere richiamano una sorta di logica con i dati, è consigliabile usare tale logica nel modello di visualizzazione. Ma non li si vuole gestire come eventi Clicked e Selected nel code-behind, se possibile. Si vuole essere il più possibile nel modello di visualizzazione, in questo modo è testabile.

Usare il Command pattern

Molti dei controlli MAUI .NET che dispongono di questo tipo di interazione supportano l'associazione a una proprietà che espone un'interfaccia ICommand. Questa proprietà è probabilmente denominata Command. Il controllo Button è un esempio:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" />

Il controllo sa quando richiamare il comando. Ad esempio, un pulsante richiama il comando quando viene premuto. Il comando in questo esempio è associato alla proprietà GiveBonus del modello di visualizzazione. Il tipo di proprietà deve implementare l'interfaccia ICommand. Il codice sarà simile al seguente esempio:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    ...
}

L'interfaccia ICommand ha un metodo Execute che viene chiamato quando si preme il pulsante. In questo modo ICommand.Execute sostituisce direttamente il codice per la gestione degli eventi Button.Click.

L'interfaccia ICommand completa include altri due metodi: CanExecute e CanExecuteChanged che sono usati per determinare se un controllo deve essere visualizzato come abilitato o disabilitato.

Un pulsante, ad esempio, può essere visualizzato come disattivato se CanExecute restituisce false.

Ecco l'aspetto dell'interfaccia ICommand in C#:

public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

Usare la classe Command

Questo modello di comando consente di mantenere una netta separazione del comportamento dell'interfaccia utente dall'implementazione dell'interfaccia utente. Può tuttavia complicare il codice qualora fosse necessario creare una classe separata per implementare il gestore di ogni evento.

Anziché creare diverse classi personalizzate che implementano l'interfaccia, è comune usare Command o Command<T>. Queste classi implementano ICommand ma ne espongono il comportamento come proprietà nel modello di visualizzazione che è possibile impostare. In questo modo, è possibile implementare la proprietà GiveBonus descritta in precedenza interamente all'interno della classe del modello di visualizzazione:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    public EmployeeViewModel(Employee model)
    {
        GiveBonusCommand = new Command(GiveBonusExecute, GiveBonusCanExecute)
    }

    void GiveBonusExecute()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
    }
}

Nel codice seguente il comportamento Execute è fornito dal metodo GiveBonusExecute. Mentre CanExecute è fornito da GiveBonusCanExecute. I delegati a questi metodi vengono passati al costruttore Command. Questo esempio non include l'implementazione di CanExecuteChanged.

Semplificare con MVVM Toolkit

La libreria MVVM Toolkit contiene implementazioni di ICommand note come RelayCommand e AsyncRelayCommand. Fornisce anche generatori di origine per semplificare ulteriormente questo codice. Nell'esempio seguente, l'GiveBonusCommand verrà generata impostando sia il metodo da chiamare per eseguire sia quello da chiamare per vedere se è possibile l'esecuzione. L'attributo [RelayCommand] viene usato nel metodo GiveBonus e genererà GiveBonusCommand. Inoltre, impostando la proprietà CanExecute dell'attributo sul nome del metodo che si vuole associare al metodo CanExecute del ICommand, verrà generato il codice per impostarlo.

public partial class EmployeeViewModel : ObservableObject
{
    public EmployeeViewModel(Employee model)
    {
    }

    [RelayCommand(CanExecute = nameof(GiveBonusCanExecute))]
    void GiveBonus()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
        return true;
    }
}

MVVM Toolkit gestisce anche metodi async, comuni nella programmazione .NET.

Comandi con parametri

L'interfaccia ICommand accetta un parametro object per i metodi CanExecute e Execute. .NET MAUI implementa questa interfaccia senza alcun controllo dei tipi tramite la classe Command. I delegati collegati al comando devono eseguire un controllo del tipo personalizzato per assicurarsi che venga passato il parametro corretto. .NET MAUI fornisce anche l'implementazione Command<T> in cui si imposta il tipo di parametro previsto. Quando si crea un comando che accetta un singolo tipo di parametro, usare Command<T>.

I controlli MAUI .NET che implementano il modello di comando forniscono la proprietà CommandParameter. Impostando questa proprietà, è possibile passare un parametro al comando quando viene richiamato con Execute o quando il comando controlla lo stato del metodo CanExecute.

In questo esempio, il valore stringa "25" viene inviato al comando:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" CommandParameter="25" />

Il comando deve interpretare e convertire il parametro stringa. Esistono molti modi per fornire un parametro fortemente tipizzato.

  • Anziché usare la sintassi degli attributi per definire CommandParameter, usare gli elementi XAML.

    <Button Text="Give Bonus" Command="{Binding GiveBonusCommand}">
        <Button.CommandParameter>
            <x:Int32>25</x:Int32>
        </Button.CommandParameter>
    </Button>
    
  • Associare l'oggetto a un'istanza CommandParameter del tipo corretto.

  • Se l'oggetto CommandParameter è associato al tipo non corretto, applicare un convertitore per convertire il valore nel tipo corretto.

Verificare le conoscenze

1.

Qual è lo scopo dell'interfaccia ICommand?

2.

In che modo le classi Command o Command<T> semplificano l'uso dell'interfaccia ICommand?

3.

Qual è il ruolo della proprietà CommandParameter nei controlli MAUI .NET?