web-dev-qa-db-fra.com

Qui doit contrôler la navigation dans une application MVVM?

Exemple # 1: J'ai une vue affichée dans mon application MVVM (utilisons Silverlight pour les besoins de la discussion) et je clique sur un bouton qui devrait m'amener à une nouvelle page.

Exemple # 2: Cette même vue a un autre bouton qui, lorsque vous cliquez dessus, devrait ouvrir une vue détaillée dans une fenêtre enfant (boîte de dialogue).

Nous savons qu'il y aura des objets Command exposés par notre ViewModel lié aux boutons avec des méthodes qui répondent au clic de l'utilisateur. Mais alors quoi? Comment terminons-nous l'action? Même si nous utilisons un soi-disant NavigationService, que disons-nous?

Pour être plus précis, dans un modèle View-first traditionnel (comme les schémas de navigation basés sur des URL comme sur le Web ou le cadre de navigation intégré SL), les objets Command devraient savoir quelle vue afficher ensuite. Cela semble franchir la ligne quand il s'agit de la séparation des préoccupations promues par le modèle.

D'un autre côté, si le bouton n'était pas connecté à un objet Command et se comportait comme un lien hypertexte, les règles de navigation pouvaient être définies dans le balisage. Mais voulons-nous que les vues contrôlent le flux des applications et la navigation n'est-elle pas simplement un autre type de logique métier? (Je peux dire oui dans certains cas et non dans d'autres.)

Pour moi, l'implémentation utopique du modèle MVVM (et j'en ai entendu d'autres professer cela) consisterait à câbler le ViewModel de telle sorte que l'application puisse fonctionner sans tête (c'est-à-dire sans vues). Cela fournit la plus grande surface pour les tests basés sur le code et fait des vues un véritable habillage sur l'application. Et mon ViewModel ne devrait pas se soucier s'il s'affiche dans la fenêtre principale, un panneau flottant ou une fenêtre enfant, n'est-ce pas?

Selon cette approbation, il appartient à un autre mécanisme au moment de l'exécution de "lier" quelle vue doit être affichée pour chaque ViewModel. Mais que faire si nous voulons partager une vue avec plusieurs ViewModels ou vice versa?

Donc, étant donné la nécessité de gérer la relation View-ViewModel afin que nous sachions quoi afficher quand ainsi que la nécessité de naviguer entre les vues, y compris l'affichage des fenêtres/boîtes de dialogue enfants, comment pouvons-nous vraiment accomplir cela dans le modèle MVVM?

33
SonOfPirate

Par souci de fermeture, j'ai pensé publier la direction que j'ai finalement choisie pour résoudre ce problème.

La première décision a été de tirer parti du cadre de navigation de page Silverlight fourni dès le départ. Cette décision était basée sur plusieurs facteurs, notamment la connaissance du fait que ce type de navigation est repris par Microsoft dans les applications Windows 8 Metro et est compatible avec la navigation dans les applications Phone 7.

Pour le faire fonctionner, j'ai ensuite examiné le travail effectué par ASP.NET MVC avec la navigation basée sur les conventions. Le contrôle Frame utilise des URI pour localiser la "page" à afficher. La similitude a permis d'utiliser une approche similaire basée sur des conventions dans l'application Silverlight. L'astuce consistait à tout faire fonctionner ensemble de manière MVVM.

La solution est le NavigationService. Ce service expose plusieurs méthodes, telles que NavigateTo et Back, que ViewModels peut utiliser pour lancer un changement de page. Lorsqu'une nouvelle page est demandée, le NavigationService envoie un CurrentPageChangedMessage à l'aide de la fonction MVVMLight Messenger.

La vue qui contient le contrôle Frame a son propre ViewModel défini comme DataContext qui écoute ce message. Une fois reçu, le nom de la nouvelle vue est placé dans une fonction de mappage qui applique nos règles de convention et défini sur la propriété CurrentPage. La propriété Source du contrôle Frame est liée à la propriété CurrentPage. Par conséquent, la définition de la propriété met à jour la source et déclenche la navigation.

Revenons au NavigationService. La méthode NavigateTo accepte le nom de la page cible. Pour vous assurer que les ViewModels n'ont aucun problème d'interface utilisateur, le nom utilisé est le nom du ViewModel à afficher. J'ai en fait créé une énumération qui a un champ pour chaque ViewModel navigable comme aide et pour éliminer les chaînes magiques partout dans l'application. La fonction de mappage que j'ai mentionnée ci-dessus supprimera le suffixe "ViewModel" du nom, ajoutera "Page" au nom et définira le nom complet sur "Vues {Nom} Page.xaml".

Ainsi, par exemple, pour accéder à la vue des détails du client, je peux appeler:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

La valeur de CustomerDetails est "CustomerDetailsViewModel" qui est mappée sur "Views\CustomerDetailsPage.xaml".

La beauté de cette approche est que l'interface utilisateur est complètement découplée des ViewModels mais que nous avons une prise en charge complète de la navigation. Cependant, je peux maintenant refaire ma demande et à chaque fois que je le juge bon sans aucun changement de code.

J'espère que l'explication aide.

6
SonOfPirate

La navigation doit toujours être gérée dans le ViewModel.

Vous êtes sur la bonne voie en pensant que la mise en œuvre parfaite du modèle de conception MVVM signifierait que vous pourriez exécuter votre application entièrement sans vues, et vous ne pouvez pas le faire si vos vues contrôlent votre navigation.

J'ai généralement un ApplicationViewModel, ou ShellViewModel, qui gère l'état général de mon application. Cela inclut le CurrentPage (qui est un ViewModel) et le code de gestion ChangePageEvents. (Il est également souvent utilisé pour d'autres objets à l'échelle de l'application tels que CurrentUser ou ErrorMessages)

Donc, si un ViewModel, n'importe où, diffuse une ChangePageEvent(new SomePageViewModel), le ShellViewModel récupérera ce message et basculera le CurrentPage sur la page spécifiée dans le message.

J'ai en fait écrit un article de blog sur Navigation avec MVVM si vous êtes intéressé

21
Rachel

Semblable à ce que Rachel a dit, mon application principalement MVVM a un Presenter pour gérer les commutateurs entre les fenêtres ou les pages. Rachel appelle cela un ApplicationViewModel, mais d'après mon expérience, il doit généralement faire plus que simplement être une cible contraignante (comme recevoir des messages, créer Windows, etc.) donc c'est techniquement plus comme un Presenter ou Controller.

Dans mon application, mon Presenter commence par un CurrentViewModel. Le Presenter intercepte toutes les communications entre le View et le ViewModel. Une des choses que ViewModel peut faire pendant une interaction est de renvoyer un nouveau ViewModel, ce qui signifie qu'une nouvelle page ou un nouveau Window doit être affiché. Le Presenter se charge de créer ou d'écraser le View pour le nouveau ViewModel et de définir le DataContext.

Le résultat d'une action peut également être qu'un ViewModel est "terminé", auquel cas le Presenter le détecte et ferme la fenêtre, ou fait disparaître ce ViewModel du = VM pile et revient à l'affichage de la page précédente.

2
Scott Whitlock