it-swarm.dev

Wer sollte die Navigation in einer MVVM-Anwendung steuern?

Beispiel 1: Ich habe eine Ansicht in meiner MVVM-Anwendung angezeigt (verwenden wir Silverlight für die Zwecke der Diskussion) und klicke auf eine Schaltfläche, die mich zu einer neuen Seite führen soll.

Beispiel 2: Dieselbe Ansicht verfügt über eine weitere Schaltfläche, die beim Klicken eine Detailansicht in einem untergeordneten Fenster (Dialogfeld) öffnen soll.

Wir wissen, dass unser ViewModel Befehlsobjekte verfügbar macht, die mit Methoden an die Schaltflächen gebunden sind, die auf den Klick des Benutzers reagieren. Aber was dann? Wie schließen wir die Aktion ab? Was sagen wir, selbst wenn wir einen sogenannten NavigationService verwenden?

Genauer gesagt müssten in einem herkömmlichen View-first-Modell (wie URL-basierten Navigationsschemata wie im Web oder dem in SL integrierten Navigationsframework) die Befehlsobjekte wissen, welche Ansicht als Nächstes angezeigt werden soll. Das scheint die Grenze zu überschreiten, wenn es um die Trennung von Bedenken geht, die durch das Muster gefördert werden.

Wenn die Schaltfläche jedoch nicht mit einem Befehlsobjekt verbunden war und sich wie ein Hyperlink verhält, können die Navigationsregeln im Markup definiert werden. Aber möchten wir, dass die Ansichten den Anwendungsfluss steuern und ist die Navigation nicht nur eine andere Art von Geschäftslogik? (Ich kann in einigen Fällen ja und in anderen nein sagen.)

Für mich besteht die utopische Implementierung des MVVM-Musters (und ich habe gehört, dass andere dies bekennen) darin, das ViewModel so zu verkabeln, dass die Anwendung kopflos ausgeführt werden kann (d. H. Keine Ansichten). Dies bietet die größte Oberfläche für codebasierte Tests und macht die Ansichten zu einem echten Skin für die Anwendung. Und meinem ViewModel sollte es egal sein, ob es im Hauptfenster, in einem schwebenden Bedienfeld oder in einem untergeordneten Fenster angezeigt wird, oder?

Nach diesem Ansatz liegt es an einem anderen Mechanismus zur Laufzeit, zu binden, welche Ansicht für jedes ViewModel angezeigt werden soll. Was aber, wenn wir eine Ansicht mit mehreren ViewModels teilen möchten oder umgekehrt?

Angesichts der Notwendigkeit, die View-ViewModel-Beziehung zu verwalten, damit wir wissen, was angezeigt werden soll, wenn zwischen den Ansichten navigiert werden muss, einschließlich der Anzeige untergeordneter Fenster/Dialoge, wie können wir dies wirklich im MVVM-Muster erreichen?

33
SonOfPirate

Zum Zwecke des Abschlusses dachte ich, ich würde die Richtung veröffentlichen, die ich schließlich gewählt hatte, um dieses Problem zu lösen.

Die erste Entscheidung war, das sofort einsatzbereite Silverlight-Seitennavigations-Framework zu nutzen. Diese Entscheidung beruhte auf mehreren Faktoren, einschließlich des Wissens, dass diese Art der Navigation von Microsoft in Windows 8 Metro-Apps übertragen wird und mit der Navigation in Phone 7-Apps übereinstimmt.

Damit es funktioniert, habe ich mir als nächstes die Arbeit angesehen, die ASP.NET MVC mit der konventionellen Navigation geleistet hat. Das Frame-Steuerelement verwendet URIs, um die anzuzeigende 'Seite' zu finden. Die Ähnlichkeit bot die Möglichkeit, einen ähnlichen konventionellen Ansatz in der Silverlight-App zu verwenden. Der Trick bestand darin, dass alles auf MVVM-Weise zusammenarbeitet.

Die Lösung ist der NavigationService. Dieser Dienst stellt verschiedene Methoden wie NavigateTo und Back zur Verfügung, mit denen ViewModels einen Seitenwechsel initiieren kann. Wenn eine neue Seite angefordert wird, sendet der NavigationService eine CurrentPageChangedMessage mithilfe der MVVMLight Messenger-Funktion.

Für die Ansicht, die das Frame-Steuerelement enthält, ist ein eigenes ViewModel als DataContext festgelegt, der auf diese Nachricht wartet. Beim Empfang wird der Name der neuen Ansicht einer Zuordnungsfunktion unterzogen, die unsere Konventionsregeln anwendet und auf die CurrentPage-Eigenschaft festgelegt wird. Die Source-Eigenschaft des Frame-Steuerelements ist an die CurrentPage-Eigenschaft gebunden. Infolgedessen aktualisiert das Festlegen der Eigenschaft die Quelle und löst die Navigation aus.

Zurück zum NavigationService. Die NavigateTo-Methode akzeptiert den Namen der Zielseite. Um sicherzustellen, dass die ViewModels keine Bedenken hinsichtlich der Benutzeroberfläche haben, wird als Name der Name des anzuzeigenden ViewModels verwendet. Ich habe tatsächlich eine Aufzählung erstellt, die ein Feld für jedes navigierbare ViewModel als Hilfsmittel enthält, um magische Zeichenfolgen in der gesamten App zu entfernen. Die oben erwähnte Zuordnungsfunktion entfernt das Suffix "ViewModel" vom Namen, hängt "Page" an den Namen an und setzt den vollständigen Namen auf "Views {Name} Page.xaml".

Um beispielsweise zur Ansicht mit den Kundendetails zu navigieren, kann ich Folgendes anrufen:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

Der Wert von CustomerDetails ist "CustomerDetailsViewModel", das "Views\CustomerDetailsPage.xaml" zugeordnet ist.

Das Schöne an diesem Ansatz ist, dass die Benutzeroberfläche vollständig von den ViewModels entkoppelt ist und wir dennoch volle Navigationsunterstützung haben. Ich kann meine Anwendung jetzt jedoch und wann immer ich es für richtig halte, ohne Codeänderungen neu gestalten.

Hoffe die Erklärung hilft.

6
SonOfPirate

Die Navigation sollte immer im ViewModel erfolgen.

Sie sind auf dem richtigen Weg, wenn Sie glauben, dass die perfekte Implementierung des MVVM-Entwurfsmusters bedeuten würde, dass Sie Ihre Anwendung vollständig ohne Ansichten ausführen könnten, und Sie können dies nicht tun, wenn Ihre Ansichten Ihre Navigation steuern.

Normalerweise habe ich ein ApplicationViewModel oder ShellViewModel, das den Gesamtstatus meiner Anwendung behandelt. Dies beinhaltet das CurrentPage (das ein ViewModel ist) und den Code für die Behandlung von ChangePageEvents. (Es wird häufig auch für andere anwendungsweite Objekte wie den CurrentUser oder ErrorMessages verwendet.)

Wenn also ein ViewModel irgendwo ein ChangePageEvent(new SomePageViewModel) sendet, nimmt das ShellViewModel diese Nachricht auf und wechselt das CurrentPage zu einer beliebigen Seite, die in der Nachricht angegeben wurde.

Ich habe tatsächlich einen Blog-Beitrag über Navigation mit MVVM geschrieben, wenn Sie interessiert sind

21
Rachel

Ähnlich wie Rachel gesagt hat, hat meine meistens MVVM-Anwendung ein Presenter, um das Wechseln zwischen Fenstern oder Seiten zu handhaben. Rachel nennt dies ein ApplicationViewModel, aber meiner Erfahrung nach muss es im Allgemeinen mehr als nur ein verbindliches Ziel sein (wie das Empfangen von Nachrichten, das Erstellen von Windows usw.), sodass es technisch eher einem herkömmlichen Presenter oder Controller.

In meiner Anwendung beginnt mein Presenter mit einem CurrentViewModel. Das Presenter fängt die gesamte Kommunikation zwischen dem View und dem ViewModel ab. Eines der Dinge, die ViewModel während einer Interaktion tun kann, ist die Rückgabe eines neuen ViewModel, was bedeutet, dass eine neue Seite oder ein neues Window angezeigt werden sollte. Das Presenter kümmert sich darum, das View für das neue ViewModel zu erstellen oder zu überschreiben und das DataContext festzulegen.

Das Ergebnis einer Aktion kann auch sein, dass ein ViewModel "vollständig" ist. In diesem Fall erkennt das Presenter dies und schließt das Fenster oder öffnet dieses ViewModel aus dem = VM stapelt und kehrt zur Anzeige der vorherigen Seite zurück.

2
Scott Whitlock