Chi dovrebbe controllare la navigazione in un'applicazione MVVM?


33

Esempio n. 1: ho una vista visualizzata nella mia applicazione MVVM (usiamo Silverlight ai fini della discussione) e faccio clic su un pulsante che dovrebbe portarmi in una nuova pagina.

Esempio n. 2: quella stessa vista ha un altro pulsante che, quando si fa clic, dovrebbe aprire una vista dei dettagli in una finestra figlio (finestra di dialogo).

Sappiamo che ci saranno oggetti Command esposti dal nostro ViewModel associati ai pulsanti con metodi che rispondono al clic dell'utente. Ma allora? Come completiamo l'azione? Anche se utilizziamo un cosiddetto NavigationService, cosa stiamo dicendo?

Per essere più specifici, in un modello tradizionale Visualizza per primo (come gli schemi di navigazione basati su URL come sul web o il framework di navigazione integrato di SL) gli oggetti Comando dovrebbero sapere quale Vista visualizzare successivamente. Ciò sembra oltrepassare il limite quando si tratta della separazione delle preoccupazioni promossa dal modello.

D'altra parte, se il pulsante non era collegato a un oggetto Command e si comportava come un collegamento ipertestuale, le regole di navigazione potevano essere definite nel markup. Ma vogliamo che le viste controllino il flusso delle applicazioni e la navigazione non sia solo un altro tipo di logica aziendale? (Posso dire di sì in alcuni casi e no in altri.)

Per me, l'implementazione utopica del modello MVVM (e ho sentito che altri lo professano) sarebbe quella di avere ViewModel cablato in modo tale che l'applicazione possa funzionare senza testa (cioè senza viste). Ciò fornisce la maggior parte della superficie per i test basati su codice e rende le viste una vera skin per l'applicazione. E il mio ViewModel non dovrebbe importare se visualizzato nella finestra principale, in un pannello mobile o in una finestra figlio, dovrebbe?

Secondo questo approccio, spetta ad altri meccanismi in fase di esecuzione "associare" ciò che View dovrebbe essere visualizzato per ciascun ViewModel. E se volessimo condividere una vista con più ViewModel o viceversa?

Quindi, data la necessità di gestire la relazione View-ViewModel in modo da sapere cosa visualizzare quando, insieme alla necessità di navigare tra le viste, inclusa la visualizzazione di finestre / finestre di dialogo figlio, come possiamo realizzarlo nel modello MVVM?

Risposte:


21

La navigazione deve essere sempre gestita in ViewModel.

Sei sulla buona strada pensando che la perfetta implementazione del modello di progettazione MVVM significherebbe che potresti eseguire la tua applicazione completamente senza Views e non puoi farlo se le tue Views controllano la tua Navigazione.

Di solito ho un ApplicationViewModel, o ShellViewModel, che gestisce lo stato generale della mia applicazione. Ciò include CurrentPage(che è un ViewModel) e il codice per la gestione ChangePageEvents. (Viene spesso utilizzato anche per altri oggetti a livello di applicazione come CurrentUser o ErrorMessages)

Quindi, se qualche ViewModel, ovunque, trasmette un ChangePageEvent(new SomePageViewModel), lo ShellViewModelprenderà quel messaggio e passerà CurrentPagealla pagina specificata nel messaggio.

In realtà ho scritto un post sul blog sulla navigazione con MVVM se sei interessato


2
Approccio interessante Quattro commenti: 1) Silverlight non supporta la proprietà DataType su DataTemplate, quindi non è possibile mappare DataTemplate su ViewModel in SL. 2) Questo non affronta le molte possibilità tra Views e ViewModels. 3) Non gestisce le finestre figlio (o almeno non vedo come). 4) Richiede un accoppiamento stretto tra l'applicazione / Shell ViewModel e i suoi figli (nipoti, ecc.) Se ho 40 pagine nella mia app, questo ViewModel sarebbe un modo ingombrante da gestire.
SonOfPirate,

Detto questo, è sicuramente qualcosa da considerare.
SonOfPirate,

@SonOfPirate 1) Silverlight non supporta la mappatura DataTemplate implicita (ancora), tuttavia supporta a DataTemplateSelector, che è quello che di solito uso per le app Silverlight. 2) L'ho usato in molte situazioni prima. Ad esempio, un ViewModel può avere più viste oppure una vista può essere associata a più ViewModel. Per un esempio del primo, vedi rachel53461.wordpress.com/2011/05/28/… . Per i successivi, devi solo specificare che più ViewModels sono associati alla stessa vista in DataTemplateSelector.
Rachel,

3) Questo è solo un esempio di base. Se sapessi che la tua applicazione sarebbe stata più finestre, ovviamente modificheresti ShellViewModel per gestire più CurrentPages4) Ancora una volta, questo era solo un esempio di base. In realtà, i miei PageViewModel sono tutti basati su una classe di base, quindi ShellViewModel funziona solo con la classe o l'interfaccia di base, come IPageViewModel. Davvero il più grande disordinato pezzo di mappatura è il DataTemplateSelector che dovrebbe mappare da 40 Visualizzazioni a 40 ViewModels.
Rachel,

@SonOfPirate Spero che abbia risposto ad alcune delle tue domande. Sentiti libero di cercarmi in chat se ne hai altri :)
Rachel,

6

Per motivi di chiusura, ho pensato di pubblicare la direzione che alla fine ho scelto di risolvere questo problema.

La prima decisione è stata quella di sfruttare il framework di navigazione della pagina Silverlight fornito immediatamente. Questa decisione si basava su diversi fattori tra cui la consapevolezza che questo tipo di navigazione viene portato avanti da Microsoft nelle app Metro di Windows 8 ed è coerente con la navigazione nelle app Phone 7.

Per farlo funzionare, ho successivamente esaminato il lavoro svolto da ASP.NET MVC con la navigazione basata su convenzioni. Il controllo Frame utilizza gli URI per individuare la 'pagina' da visualizzare. La somiglianza ha offerto l'opportunità di utilizzare un approccio simile basato su una convenzione nell'app Silverlight. Il trucco era far funzionare tutto insieme in modo MVVM.

La soluzione è NavigationService. Questo servizio espone diversi metodi, come NavigateTo e Back, che ViewModels può utilizzare per avviare una modifica di pagina. Quando viene richiesta una nuova pagina, NavigationService invia un CurrentPageChangedMessage utilizzando la funzione MVVMLight Messenger.

La vista che contiene il controllo Frame ha il proprio ViewModel impostato come DataContext in ascolto per questo messaggio. Una volta ricevuto, il nome della nuova vista viene inserito attraverso una funzione di mappatura che applica le nostre regole della convenzione e impostato sulla proprietà CurrentPage. La proprietà Source del controllo Frame è associata alla proprietà CurrentPage. Di conseguenza, l'impostazione della proprietà aggiorna l'origine e attiva la navigazione.

Tornando al NavigationService. Il metodo NavigateTo accetta il nome della pagina di destinazione. Per assicurarsi che ViewModels non abbia problemi di interfaccia utente, il nome utilizzato è il nome del ViewModel da visualizzare. In realtà ho creato un elenco che ha un campo per ogni ViewModel navigabile come aiuto e per eliminare le stringhe magiche in tutta l'app. La funzione di mappatura che ho menzionato sopra rimuoverà il suffisso "ViewModel" dal nome, aggiungi "Pagina" al nome e imposta il nome completo su "Views {Name} Page.xaml".

Quindi, ad esempio, per accedere alla vista dei dettagli del cliente, posso chiamare:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

Il valore di CustomerDetails è "CustomerDetailsViewModel" che è mappato su "Views \ CustomerDetailsPage.xaml".

Il bello di questo approccio è che l'interfaccia utente è completamente disaccoppiata dai ViewModels eppure abbiamo il supporto completo per la navigazione. Ora posso riassegnare la mia applicazione comunque e ogni volta che lo ritengo opportuno senza alcuna modifica del codice.

Spero che la spiegazione sia d'aiuto.


2

Simile a quello che ha detto Rachel, la mia applicazione per lo più MVVM ha un modo Presenterper gestire gli scambi tra finestre o pagine. Rachel lo chiama un ApplicationViewModel, ma secondo la mia esperienza, in genere deve fare molto di più di un semplice obiettivo vincolante (come ricevere messaggi, creare Windows, ecc.) Quindi tecnicamente è più simile a un tradizionale Presentero Controller.

Nella mia applicazione, la mia Presenterinizia con a CurrentViewModel. Il Presenterintercetta tutte le comunicazioni tra il Viewe il ViewModel. Una delle cose che ViewModelpuò fare durante un'interazione è restituire una nuova ViewModel, il che significa che Windowdovrebbe essere visualizzata una nuova pagina o una nuova . Si Presenteroccupa di creare o sovrascrivere il Viewper il nuovo ViewModele impostare il DataContext.

Il risultato di un'azione può anche essere che a ViewModelè "completo", nel qual caso lo Presenterrileva e chiude la finestra, oppure lo fa ViewModeluscire dallo stack della VM e torna alla visualizzazione della pagina precedente.


Come fa il relatore a sapere quale vista visualizzare?
SonOfPirate,

1
@SonOfPirate: normalmente viene eseguito tramite i meccanismi WPF. Il Presentersolo attacca il reso ViewModelnell'albero visivo e WPF afferra l'appropriato View, aggancia il DataContexte lo inserisce invece nell'albero visivo. È possibile farlo utilizzando DataTemplates che dichiarano il ViewModeltipo di rendering, oppure è possibile creare un selettore di modelli di dati personalizzato. È ancora nell'ambito delle funzionalità di WPF.
Scott Whitlock,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.