Condividi tramite


Il presente articolo è stato tradotto automaticamente.

MVVM

Esecuzione dei comandi WPF con il modello di macchina a stati

Tarquin Vaughan -Scott

Windows Presentation Foundation (WPF) è un potente framework dominante che consente di separare l'interfaccia utente e la logica di comando. Quando si utilizza il modello di progettazione Model-View-ViewModel (MVVM), un comando è esposto il ViewModel come una proprietà che implementa l'interfaccia ICommand. Controlli della vista si legano a queste proprietà. Quando un utente interagisce con tale controllo, esegue il comando.

Come sempre, il diavolo è nei dettagli. La vera sfida non è in esecuzione il comando, ma assicurandosi che viene eseguito quando il ViewModel è in uno stato valido per quell'azione. In genere, viene implementata la convalida "possono eseguire" con un'espressione condizionale usando le variabili locali come IsLoading, CanDoXYZ, SomeObject! = null, e così via. Ogni nuovo stato richiede ulteriori condizioni che necessitano di valutazione, così questa strategia può rapidamente diventare eccessivamente complessa.

Una macchina di stato risolve il problema "possono eseguire", come esso limita le azioni consentite per uno stato specifico. Associazione di comandi direttamente a macchina fornisce una buona soluzione a questo difficile problema.

Questo articolo intende dimostrare come progettare e implementare una macchina a stati analizzando l'applicazione. Esso inoltre vi mostrerà come associare i comandi alla macchina dello stato. Ci sono anche alcuni vantaggi aggiuntivi ad esempio impedendo la rientranza di metodi asincroni e mostrare e nascondere i controlli per stati specifici.

Macchine dello stato

Macchine di stato sono disponibili in diversi gusti, ma sono essenzialmente un modello di progettazione che rappresenta un processo che si muove da uno stato a altro. Azioni dell'utente (chiamate anche trigger) causano la macchina dello stato di transizione tra Stati. Regole limitano le azioni consentite per ogni stato.

Macchine a stati finiti permettono solo uno stato alla volta. Stato gerarchico macchine permettono di Stati e stati secondari. Macchine a stati gerarchici sono spesso più utili perché gli sub-stati ereditano tutti gli attributi del loro super-stato. Ciò può ridurre la quantità di configurazione richiesta.

Si consideri ad esempio un utente facendo clic su un pulsante di ricerca: Le transizioni di applicazione a uno stato di ricerca. Mentre in questo stato, l'utente dovrebbe essere evitato di eseguire altre azioni (con la possibile eccezione di un'azione di annullamento). Lo stato della ricerca avrebbe quindi una regola che impedisce o ignora tutte le azioni ad eccezione dell'azione di annullamento. Alcune implementazioni della macchina dello stato anche consentono l'entrata e uscita di azioni. Si può avere questa logica eseguita quando uno stato è entrato e uscito.

Struttura di comando

Come un genio in una bottiglia, comandi sono lì per eseguire gli ordini del loro padrone, ma che cosa è un comando? Comandi hanno la loro origine nelle interfacce della riga di comando, in cui l'utente è tenuto a digitare in una direttiva che viene quindi interpretata dall'applicazione.

Questo concetto si è evoluto nelle moderne interfacce astratto prevista azione di un utente. Ad esempio, se un utente vuole copiare del testo, il comando è copia testo. L'utente può raggiungere questo obiettivo facendo clic su una voce di menu, cliccando col tasto destro il pulsante del mouse o usando anche i comandi da tastiera (CTRL + C). L'implementazione del comando dipende l'applicazione sottostante e il quadro su cui è costruito.

WPF implementa il concetto di comando tramite l'interfaccia ICommand. Questo è parte di Microsoft .NET Framework. Questa interfaccia ha due metodi e un evento:

  • void Execute (parametro object) — questo esegue il codice quando viene richiamato il comando.
  • bool CanExecute (parametro object) — determina se il comando può essere richiamato.
  • evento EventHandler CanExecuteChanged — informa il quadro che hanno cambiato condizioni che riguardano il metodo CanExecute. Questo è generalmente gestito dal framework di WPF.

Fondamentalmente, un'origine comando come una voce di menu o pulsante implementa l'interfaccia ICommandSource. Questa interfaccia dispone di una proprietà denominata comando di tipo ICommand. Da questa proprietà di associazione a un'implementazione ICommand su ViewModel, il controllo verrà richiamato il metodo Execute. Hai anche abilitato e disabilitato in base al risultato del metodo CanExecute. Se un controllo che agisce come una fonte di comando non ha una proprietà di ICommand (che, purtroppo, è abbastanza comune), quindi è possibile utilizzare un evento alla tecnica di comando.

Le implementazioni di ICommand costruito .NET Framework sono l'oggetto RoutedCommand e RoutedUICommand. Questi eventi su e giù per la struttura ad albero visuale di instradare e non sono adatti per l'uso con il pattern MVVM. Il RelayCommand da Josh Smith e il DelegateCommand che fa parte del quadro prisma sono comunemente usati implementazioni per il pattern MVVM.

Il codice sorgente che accompagna questo articolo contiene una soluzione di Visual Studio 2013 che illustra i principi della macchina e come utilizzarlo per gestire i comandi. L'applicazione di esempio consente a un utente attraverso un elenco di collaboratori di ricerca, selezionare un dipendente e quindi visualizzare una finestra di dialogo per la modifica dettagli dipendente (vedere Figura 1).

l'elenco dei dipendenti caricati e pronto per la modifica
Figura 1 l'elenco dei dipendenti caricati e pronto per la modifica

Progettazione della macchina a Stati

Il primo passo nel processo di progettazione è definire la sezione dell'applicazione utilizzando un diagramma di flusso o un diagramma di stato. Figura 2 Mostra un diagramma diagramma di flusso per la schermata di gestione dei dipendenti. Esso comprende anche blocchi che indicano un processo potenzialmente di lunga durata come Searching. È necessario gestirli in modo speciale e impedire all'utente di eseguire nuovamente l'operazione mentre è occupato. Creazione di questi blocchi intermedi "occupati" è particolarmente importante con la nuova funzionalità di async in .NET Framework, come controllo restituisce all'utente e potenzialmente potrebbe provare ed eseguire la stessa azione nuovamente con conseguente rientro.

diagramma di flusso mostrando il processo dello schermo dipendente responsabile
Figura 2 diagramma di flusso mostrando il processo dello schermo dipendente responsabile

Il secondo passo del processo di progettazione è definire tutti i comandi che consentono all'utente di interagire con l'applicazione. Dalla schermata di gestione dipendente, l'utente può ricerca, modificare un dipendente e poi termina modifica. L'utente aziona anche il processo di selezione e deselezione di un dipendente, tuttavia, queste non sono gestite come comandi. Hanno sei gestito come associazione dati in un controllo DataGrid. I comandi sono ora mappati nelle frecce di flusso di lavoro (o flusso di controllo) nei punti pertinenti, come mostrato Figura 2.

Implementare la macchina dello stato

Non devi creare un quadro di stato macchina da zero, come ci sono molte librerie disponibili gratuitamente. L'unica opzione all'interno di .NET Framework è l'attività di Workflow Foundation (WF) stato macchina. Questo è troppo complesso per il problema che sto cercando di risolvere qui, ma è grande per i flussi di lavoro persistenti con esecuzione prolungata. Dopo alcune ricerche preliminari, ho risolto la biblioteca di macchina di stato apolide, che è disponibile come pacchetto NuGet a bit.ly/ZL58MG.

Ora posso usare il diagramma di flusso che ho creato in fase di progettazione per creare un elenco di stati (ogni blocco nel diagramma di flusso) e i trigger (ogni freccia nel diagramma di flusso). Stateless utilizza tipi generici per gli Stati e i trigger, così ho intenzione di andare avanti e utilizzare un'enumerazione per entrambi:

public enum States
{
  Start, Searching, SearchComplete, Selected, NoSelection, Editing
}
public enum Triggers
{
  Search, SearchFailed, SearchSucceeded, Select, DeSelect, Edit, EndEdit
}

Una volta le enumerazioni sono definite, è necessario configurare ciascuno stato usando l'interfaccia fluente. Le opzioni importanti sono:

  • SubstateOf (stato TState) — indica uno stato ha una super -­di stato ed erediterà tutte le sue configurazioni.
  • Permesso (TTrigger trigger, TState targetState) — consente agli Stati di transizione verso lo stato di destinazione tramite il grilletto.
  • Ignorare (trigger TTrigger) — determina lo stato di ignorare il grilletto se suo licenziato.
  • OnEntry (azione entryAction) — provoca un'azione da eseguire quando lo stato è entrato.
  • OnExit (azione exitAction) — provoca un'azione da eseguire quando lo stato è stato terminato.

Ci sarà un'eccezione se un trigger viene attivato in uno stato senza una configurazione valida generata dalla macchina a stati. Questa è la configurazione per lo stato della ricerca:

Configure(States.Searching)
  .OnEntry(searchAction)
  .Permit(Triggers.SearchSucceeded, States.SearchComplete)
  .Permit(Triggers.SearchFailed, States.Start)
  .Ignore(Triggers.Select)
  .Ignore(Triggers.DeSelect);

L'azione OnEntry esegue il processo di ricerca e i permessi consentono il trigger rilevanti al fuoco. L'ignorate impedire la vista di infornamento seleziona e deseleziona trigger quando il controllo DataGrid viene associato all'origine dati sottostante (un fastidio che accade con la maggior parte dei controlli di elenco in WPF).

La macchina di stato espone anche due metodi principali:

  • void Fire (TTrigger) — questo passa la macchina a stati utilizzando le configurazioni precedenti.
  • bool CanFire (trigger Trigger) — restituisce true se lo stato attuale permette l'innesco di essere licenziato.

Questi sono i metodi principali necessari per la creazione di comandi. Essi eseguono execute e possono eseguire logica.

Associare i comandi alla macchina statale

Il pattern MVVM espone una proprietà che implementa l'interfaccia ICommand il ViewModel. Creando questa proprietà command è ora una semplice questione di associazione i metodi Execute e CanExecute ai metodi di fuoco e CanFire della macchina dello stato, rispettivamente. Creare un metodo di estensione per centralizzare questa logica:

public static ICommand CreateCommand<TState, TTrigger>(
  this StateMachine<TState, TTrigger> stateMachine, TTrigger trigger)
    {
      return new RelayCommand
        (
          () => stateMachine.Fire(trigger),
          () => stateMachine.CanFire(trigger)
        );
    }

Una volta che questo metodo di estensione è a posto, creare proprietà di ICommand su ViewModel (fare riferimento al diagramma di flusso in Figura 2 per i comandi identificati durante la fase di analisi):

SearchCommand = StateMachine.CreateCommand(Triggers.Search);
EditCommand = StateMachine.CreateCommand(Triggers.Edit);
EndEditCommand = StateMachine.CreateCommand(Triggers.EndEdit);

Associare la vista al comando ViewModel

Per un controllo che agisce come un'origine comando, si può avere la proprietà del comando associato alla proprietà di ICommand su ViewModel. L'applicazione di esempio ha due pulsanti e voci di menu associato alle proprietà comando su ViewModel:

<Button ToolTip="Search" VerticalAlignment="Center" 
  Style="{StaticResource ButtonStyle}"
  Command="{Binding SearchCommand}">
  <Image Source="Images\Search.png"></Image>
</Button>

Ora il comando è associato direttamente alla macchina dello stato. Genererà il trigger configurato quando viene eseguito, ma più importante, esso verrà disabilitato se il grilletto non è consentito per lo stato corrente. Questo prende cura dell'ingannevole può eseguire logica ed è tutto perfettamente configurato all'interno la macchina dello stato. Figura 3 Mostra la schermata di gestione dipendente mentre è occupato di ricerca. Nota il comando di ricerca è disabilitata come il grilletto non è consentito per lo stato di ricerca di ricerca.

occupato animazione nella finestra di dialogo ricerca
Figura 3 occupato animazione nella finestra di dialogo ricerca

Benefici aggiuntivi

Una volta che tu hai implementato la macchina dello stato, lo stato è anche possibile associare altri elementi visivi. Quando la funzione di ricerca è impegnata ad eseguire (e tenere a mente questa potrebbe essere un'operazione di lunga durata all'interno di un metodo asincrono), è auspicabile per mostrare all'utente un indicatore di occupato. Figura 3 illustra un'immagine animata visualizzata solo quando la macchina a stati è nello stato Searching. Fare questo creando un convertitore personalizzato che converte un valore di visibilità dello stato macchina:

public class StateMachineVisibilityConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, CultureInfo culture)
    {
      string state = value != null ? 
        value.ToString() : String.Empty;
      string targetState = parameter.ToString();
      return state == targetState ? 
        Visibility.Visible : Visibility.Collapsed;
    }
 }

L'immagine animata viene quindi associato allo stato stato macchina utilizzando il convertitore personalizzato:

<local:AnimatedGIFControl Visibility="{Binding StateMachine.State,
                          Converter={StaticResource StateMachineConverter},
                          ConverterParameter=Searching}"/>

È possibile applicare lo stesso principio per la finestra di dialogo Modifica mostrato sopra la schermata di gestione dipendente in Figura 4. Quando la macchina a stati è in stato di modifica, la finestra di dialogo diventa visibile e l'utente può modificare i dettagli del dipendente selezionato.

finestra di dialogo Modifica stato
Figura 4 finestra di dialogo Modifica stato

Stato d'animo

Il modello della macchina dello stato non solo risolve i problemi connessi con la logica di comando, ma crea anche uno stato d'animo che consente una migliore analisi di applicazione. È facile pensare di comandi come controlli che eseguono una sorta di logica. Ciò che viene spesso trascurato è determinare quando consentire il comando da eseguire. Il modello dello stato-comando macchina risolve questi problemi nelle prime fasi del processo di progettazione. Sono naturalmente parte della configurazione della macchina dello stato. Stati macchine hanno semplificato il mio codice di ViewModel e possono fare lo stesso per le vostre applicazioni.

Riferimenti aggiuntivi

  • Stato macchina: È sorprendentemente difficile da trovare un'introduzione ben scritto per macchine a Stati, ma sviluppatore di giochi Bob Nystrom ha scritto una abbastanza buona (bit.ly/1uGxVv6).
  • MVVM WPF: Il "padre" di WPF-MVVM Josh Smith fornisce un'implementazione dell'interfaccia ICommand, il RelayCommand, in un febbraio 2009 MSDN Magazine articolo (msdn.microsoft.com/magazine/dd419663).
  • Comandi: Documentazione ufficiale MSDN su WPF comandando discute i comandi indirizzati ed eventi che fanno parte di .NET Framework (bit.ly/1mRCOTv).
  • Comandi indirizzati: Brian Noyes copre i comandi indirizzati in un settembre 2008 MSDN Magazine articolo. È un buon punto di partenza per capire la differenza tra i comandi indirizzati e il modello di comando MVVM (bit.ly/1CihBVZ).
  • Comandi, RelayCommands ed EventToCommand: Laurent Bugniondi maggio 2013 articolo viene illustrato come convertire gli eventi ai comandi, che è utile per i controlli che non implementano l'interfaccia ICommandSource (msdn.microsoft.com/magazine/dn237302).
  • Prisma Framework: La documentazione ufficiale per il framework prisma discute il MVVM e il DelegateCommand (bit.ly/1k2Q6sY).
  • Pacchetto di NuGet apolidi: Il pacchetto sul sito Web di NuGet apolide viene fornito con istruzioni per il download (bit.ly/1sXBQl2).

Tarquin Vaughan -Scott è il capo sviluppatore presso InfoVest (infovest.co.za), con sede in città del capo, Sud Africa, che crea soluzioni di gestione dati per l'industria finanziaria. I suoi interessi principali sono la progettazione di database e di architetture —­soprattutto le differenze tra transazionale e sistemi di magazzino dati. Contattarlo al tarquin@infovest.co.za.

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Nicholas Blumhardt
Nicholas Blumhardt (Stateless) è un impiegato di ex-Microsoft come parte del team di MEF