Wer sollte die Navigation in einer MVVM-Anwendung steuern?

33

Beispiel 1: In meiner MVVM-Anwendung wird eine Ansicht angezeigt (wir verwenden Silverlight für die Zwecke der Diskussion) und ich 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 Command-Objekte von unserem ViewModel verfügbar gemacht werden, die an die Schaltflächen mit Methoden gebunden sind, die auf den Klick des Benutzers reagieren. Aber was dann? Wie schließen wir die Aktion ab? Auch wenn wir einen sogenannten NavigationService nutzen, was erzählen wir davon?

Um genauer zu sein, müssten die Command-Objekte in einem herkömmlichen View-first-Modell (wie URL-basierten Navigationsschemata wie im Web oder dem integrierten SL-Navigationsframework) wissen, welche View 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 ist 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 die Navigation nicht nur eine andere Art von Geschäftslogik ist? (In einigen Fällen kann ich ja und in anderen nein sagen.)

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

Gemäß diesem Apprach ist es einem anderen Mechanismus überlassen, zur Laufzeit zu "binden", welche Ansicht für jedes ViewModel angezeigt werden soll. Aber was ist, wenn wir eine Ansicht mit mehreren ViewModels teilen möchten oder umgekehrt?

In Anbetracht der Notwendigkeit, die View-ViewModel-Beziehung zu verwalten, damit wir wissen, was angezeigt werden soll, und der Notwendigkeit, zwischen Ansichten zu navigieren, einschließlich der Anzeige von untergeordneten Fenstern / Dialogen, wie können wir dies im MVVM-Muster wirklich erreichen?

SonOfPirate
quelle

Antworten:

21

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 das können Sie nicht, wenn Ihre Ansichten Ihre Navigation steuern.

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

Wenn also ein beliebiges ViewModel irgendwo ein Broadcast sendet ChangePageEvent(new SomePageViewModel), ShellViewModelwird das diese Nachricht aufgreifen und das wechselnCurrentPage in der Nachricht angegebene Seite umgeschaltet.

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

Rachel
quelle
2
Interessanter Ansatz. Vier Kommentare: 1) Silverlight unterstützt die DataType-Eigenschaft in DataTemplate nicht, sodass das Zuordnen der DataTemplate zum ViewModel in SL nicht möglich ist. 2) Dies geht nicht auf die vielen Möglichkeiten zwischen Views und ViewModels ein. 3) Es funktioniert nicht mit untergeordneten Fenstern (oder ich verstehe zumindest nicht, wie). 4) Es erfordert eine enge Kopplung zwischen Ihrer Anwendung / Shell ViewModel und ihren Kindern (Enkelkindern usw.).
SonOfPirate
Das heißt, es ist definitiv etwas zu prüfen.
SonOfPirate
@SonOfPirate 1) Silverlight unterstützt (noch) keine implizite DataTemplate-Zuordnung, unterstützt jedoch eine DataTemplateSelector, die ich normalerweise für Silverlight-Apps verwende. 2) Ich habe das schon in vielen Situationen benutzt. Beispielsweise kann ein ViewModel mehrere Ansichten haben, oder eine Ansicht kann mehreren ViewModels zugeordnet sein. Ein Beispiel für Ersteres finden Sie unter rachel53461.wordpress.com/2011/05/28/… . Für die spätere Version müssen Sie nur angeben, dass mehrere ViewModels derselben Ansicht in Ihrem DataTemplateSelector zugeordnet sind.
Rachel
3) Das ist nur ein einfaches Beispiel. Wenn Sie wüssten, dass Ihre Anwendung aus mehreren Fenstern bestehen würde, würden Sie das ShellViewModel offensichtlich so ändern CurrentPages, dass es mehrere Fenster verarbeiten kann. 4) Auch hier war dies nur ein einfaches Beispiel. In Wirklichkeit basieren meine PageViewModels alle auf einer Basisklasse, sodass mein ShellViewModel nur mit der Basisklasse oder der Schnittstelle funktioniert, z IPageViewModel. Wirklich die größte Unordnung bei der Zuordnung ist der DataTemplateSelector, der 40 Views auf 40 ViewModels abbilden müsste.
Rachel,
@SonOfPirate Ich hoffe, dass einige Ihrer Fragen beantwortet wurden. Sie können mich
Rachel
6

Aus Gründen des Abschlusses dachte ich, ich würde die Richtung angeben, die ich letztendlich zur Lösung dieses Problems gewählt habe.

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

Als Nächstes habe ich mir die Arbeit der ASP.NET-MVC mit der konventionellen Navigation angesehen. 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 zusammenarbeitete.

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

Die Ansicht, die das Frame-Steuerelement enthält, verfügt über ein eigenes ViewModel, das als DataContext festgelegt ist, 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 setzt. 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 Navigationsservice. Die NavigateTo-Methode akzeptiert den Namen der Zielseite. Um sicherzustellen, dass die ViewModels keine Probleme mit der Benutzeroberfläche haben, wird der Name des anzuzeigenden ViewModels verwendet. Ich habe tatsächlich eine Aufzählung erstellt, die ein Feld für jedes navigierbare ViewModel als Helfer und zur Eliminierung magischer Zeichenfolgen in der gesamten App enthält. 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 zum Beispiel zur Kundendetailsicht zu navigieren, kann ich anrufen:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

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

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

Hoffe die Erklärung hilft.

SonOfPirate
quelle
2

Ähnlich wie Rachel es gesagt hat, hat meine hauptsächlich aus MVVM bestehende Anwendung eine Funktion Presenterzum Umschalten zwischen Fenstern oder Seiten. 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.), also ist es technisch eher wie ein traditionelles Presenteroder einController .

In meiner Bewerbung Presenterbeginnt meine mit einem CurrentViewModel. Das Presenterfängt die gesamte Kommunikation zwischen dem Viewund dem ab ViewModel. Eines der Dinge , die ViewModelwährend einer Interaktion tun können , ist eine neue zurückgeben ViewModel, was bedeutet , eine neue Seite oder ein neues Windowangezeigt werden soll. Das Presenterkümmert sich darum, das Viewfür das Neue zu erstellen oder zu überschreiben ViewModelund das einzustellenDataContext .

Das Ergebnis einer Aktion kann auch sein, dass a ViewModel"vollständig" ist. In diesem Fall Presentererkennt der Benutzer dies und schließt das Fenster oder entfernt es ViewModelvom VM-Stapel und kehrt zur Anzeige der vorherigen Seite zurück.

Scott Whitlock
quelle
Woher weiß der Präsentator, welche Ansicht angezeigt werden soll?
SonOfPirate
1
@SonOfPirate - Dies erfolgt normalerweise über WPF-Mechanismen. Die Presentergerade Stöcke des zurück ViewModelin der visuellen Struktur und WPF packt die entsprechenden View, bis Haken , die DataContext, und Puts , dass in der visuellen Struktur statt. Sie können dazu DataTemplates verwenden, die deklarieren, welchen ViewModelTyp sie darstellen, oder Sie können einen benutzerdefinierten Datenvorlagen-Selektor erstellen. Das liegt immer noch im Bereich der WPF-Funktionen.
Scott Whitlock