Kto powinien kontrolować nawigację w aplikacji MVVM?

33

Przykład # 1: Mam widok wyświetlany w mojej aplikacji MVVM (użyjmy Silverlight do celów dyskusji) i klikam przycisk, który powinien zabrać mnie na nową stronę.

Przykład # 2: Ten sam widok ma inny przycisk, który po kliknięciu powinien otworzyć widok szczegółów w oknie potomnym (oknie dialogowym).

Wiemy, że obiekty Command będą widoczne przez nasz ViewModel powiązany z przyciskami metodami reagującymi na kliknięcie użytkownika. Ale co wtedy? Jak ukończyć akcję? Nawet jeśli korzystamy z tak zwanej usługi nawigacji, to co mówimy?

Mówiąc ściślej, w tradycyjnym modelu Najpierw przeglądaj (takim jak schematy nawigacyjne oparte na adresach URL, takie jak w Internecie lub we wbudowanej ramie nawigacyjnej SL), obiekty Command musiałyby wiedzieć, który Widok będzie wyświetlany następnie. To wydaje się przekraczać granicę, jeśli chodzi o rozdzielenie obaw promowanych przez ten wzorzec.

Z drugiej strony, jeśli przycisk nie był podłączony do obiektu Command i zachowywał się jak hiperłącze, reguły nawigacji można zdefiniować w znacznikach. Ale czy chcemy, aby Widoki kontrolowały przepływ aplikacji i czy nawigacja nie jest tylko innym rodzajem logiki biznesowej? (W niektórych przypadkach mogę powiedzieć „tak”, a w innych „nie”).

Dla mnie utopijną implementacją wzorca MVVM (i słyszałem, jak inni to twierdzą) byłoby połączenie ViewModel w taki sposób, aby aplikacja mogła działać bez głowy (tj. Bez widoków). Zapewnia to największą powierzchnię do testowania opartego na kodzie i sprawia, że ​​widoki są prawdziwą skórką w aplikacji. A mój ViewModel nie powinien dbać o to, czy wyświetla się w oknie głównym, panelu pływającym czy oknie potomnym, prawda?

Zgodnie z tym podejściem do innego mechanizmu w środowisku wykonawczym należy „powiązanie” tego, co Widok powinien być wyświetlany dla każdego modelu ViewModel. Ale co, jeśli chcemy udostępnić widok wielu modelom ViewModels lub odwrotnie?

Więc biorąc pod uwagę potrzebę zarządzania relacją View-ViewModel, abyśmy wiedzieli, co wyświetlać, wraz z potrzebą nawigowania między widokami, w tym wyświetlania okien / dialogów potomnych, jak naprawdę możemy to osiągnąć we wzorcu MVVM?

SonOfPirate
źródło

Odpowiedzi:

21

Nawigacja powinna być zawsze obsługiwana w ViewModel.

Jesteś na dobrej drodze, myśląc, że idealna implementacja wzorca projektowego MVVM oznaczałaby, że możesz uruchomić aplikację całkowicie bez widoków, i nie możesz tego zrobić, jeśli twoje widoki kontrolują nawigację.

Zwykle mam ApplicationViewModel, lub ShellViewModel, który obsługuje ogólny stan mojej aplikacji. Obejmuje to CurrentPage(który jest ViewModel) i kod do obsługi ChangePageEvents. (Często używany jest również w przypadku innych obiektów aplikacji, takich jak CurrentUser lub ErrorMessages)

Więc jeśli jakikolwiek ViewModel, gdziekolwiek, emituje a ChangePageEvent(new SomePageViewModel), ShellViewModelodbierze tę wiadomość i przełączy na CurrentPagedowolną stronę określoną w wiadomości.

Jeśli jesteś zainteresowany, napisałem wpis na blogu o Nawigacji w MVVM

Rachel
źródło
2
Ciekawe podejście Cztery komentarze: 1) Silverlight nie obsługuje właściwości DataType na DataTemplate, więc mapowanie DataTemplate na ViewModel nie jest możliwe w SL. 2) Nie dotyczy to wielu możliwości między widokami a modelami ViewModels. 3) Nie obsługuje okien potomnych (a przynajmniej nie wiem jak). 4) Wymaga ścisłego powiązania między twoją aplikacją / powłoką ViewModel i jej dziećmi (wnukami itp.). Jeśli mam 40 stron w mojej aplikacji, ten ViewModel byłby trudny w zarządzaniu.
SonOfPirate
To powiedziawszy, to zdecydowanie coś do rozważenia.
SonOfPirate
@SonOfPirate 1) Silverlight nie obsługuje jeszcze niejawnego mapowania DataTemplate (jednak), ale obsługuje a DataTemplateSelector, co zwykle używam w aplikacjach Silverlight. 2) Używałem tego wcześniej w wielu sytuacjach. Na przykład jeden model ViewModel może mieć wiele widoków lub jeden widok może być powiązany z wieloma modelami ViewModel. Przykład tego pierwszego znajduje się na stronie rachel53461.wordpress.com/2011/05/28/… . Później musisz tylko określić, że wiele map ViewModels mapuje do tego samego widoku w DataTemplateSelector.
Rachel
3) To tylko podstawowy przykład. Gdybyś wiedział, że twoja aplikacja będzie miała wiele okien, oczywiście zmieniłbyś ShellViewModel tak, aby obsługiwał wiele CurrentPages4) Ponownie, był to tylko podstawowy przykład. W rzeczywistości wszystkie moje PageViewModels opierają się na jakiejś klasie bazowej, więc mój ShellViewModel działa tylko z klasą bazową lub interfejsem, takim jak IPageViewModel. Naprawdę największym bałaganem mapowania jest DataTemplateSelector, który musiałby zmapować 40 widoków do 40 modeli ViewModels.
Rachel
@ SonOfPirate Mam nadzieję, że odpowiedziałem na niektóre pytania. Zapraszam do szukania mnie na Czacie, jeśli masz innych :)
Rachel
6

Ze względu na zamknięcie pomyślałem, że opublikuję kierunek, w którym ostatecznie postanowiłem rozwiązać ten problem.

Pierwszą decyzją było skorzystanie z frameworku Silverlight Page Navigation dostarczonego natychmiast po wyjęciu z pudełka. Decyzja ta była oparta na kilku czynnikach, w tym na wiedzy, że ten rodzaj nawigacji jest przenoszony przez Microsoft do aplikacji Windows 8 Metro i jest zgodny z nawigacją w aplikacjach Phone 7.

Aby to zadziałało, następnie przyjrzałem się pracy ASP.NET MVC z nawigacją opartą na konwencjach. Kontrolka Frame używa identyfikatorów URI do zlokalizowania „strony” do wyświetlenia. Podobieństwo umożliwiło zastosowanie podobnego podejścia opartego na konwencjach w aplikacji Silverlight. Sztuka polegała na tym, aby wszystko działało razem w sposób MVVM.

Rozwiązaniem jest usługa nawigacji. Ta usługa udostępnia kilka metod, takich jak NavigateTo i Back, których ViewModels może użyć do zainicjowania zmiany strony. Gdy wymagana jest nowa strona, NavigationService wysyła CurrentPageChangedMessage za pomocą funkcji MVVMLight Messenger.

Widok zawierający kontrolkę Frame ma własny zestaw ViewModel jako DataContext, który nasłuchuje tej wiadomości. Po otrzymaniu nazwa nowego widoku jest poddawana funkcji mapowania, która stosuje nasze reguły konwencji i jest ustawiona na właściwość CurrentPage. Właściwość Source kontrolki Frame jest powiązana z właściwością CurrentPage. W rezultacie ustawienie właściwości aktualizuje Źródło i uruchamia nawigację.

Wracając do usługi nawigacji. Metoda NavigateTo akceptuje nazwę strony docelowej. Aby upewnić się, że ViewModels nie ma problemów z interfejsem użytkownika, używana nazwa to nazwa ViewModel do wyświetlenia. W rzeczywistości stworzyłem wyliczenie, które ma pole dla każdego nawigowalnego modelu ViewModel jako pomocnika i aby wyeliminować magiczne ciągi w całej aplikacji. Funkcja mapowania, o której wspomniałem powyżej, usuwa sufiks „ViewModel” z nazwy, dołącza „Page” do nazwy i ustawia pełną nazwę na „Views {Name} Page.xaml”.

Na przykład, aby przejść do widoku szczegółów klienta, mogę zadzwonić:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

Wartość CustomerDetails to „CustomerDetailsViewModel”, która jest odwzorowana na „Views \ CustomerDetailsPage.xaml”.

Piękno tego podejścia polega na tym, że interfejs użytkownika jest całkowicie oddzielony od modeli ViewModels, ale mamy pełną obsługę nawigacji. Teraz mogę jednak ponownie przeskalować moją aplikację i za każdym razem, gdy uznaję to za stosowne, bez żadnych zmian w kodzie.

Mam nadzieję, że wyjaśnienie to pomaga.

SonOfPirate
źródło
2

Podobnie do tego, co powiedziała Rachel, moja aplikacja głównie MVVM ma Presenterdo obsługi przełączania między oknami lub stronami. Rachel nazywa to an ApplicationViewModel, ale z mojego doświadczenia wynika, że ​​na ogół musi robić coś więcej niż tylko być wiążącym celem (takim jak odbieranie wiadomości, tworzenie systemu Windows itp.), Więc technicznie bardziej przypomina tradycyjny Presenterlub Controller.

W mojej aplikacji Presenterzaczynam od CurrentViewModel. PresenterPrzechwytuje całą komunikację między Viewa ViewModel. Jedną z rzeczy, które ViewModelmożna zrobić podczas interakcji, jest zwrócenie nowej ViewModel, co oznacza, że Windownależy wyświetlić nową stronę lub nową . PresenterDba o tworzenie lub zastępowanie Viewdla nowych ViewModeli ustawienie DataContext.

Rezultatem akcji może być również to, że a ViewModeljest „zakończone”, w którym to przypadku Presenterwykrywa to i zamyka okno lub zrywa je ViewModelze stosu maszyny wirtualnej i wraca do wyświetlania poprzedniej strony.

Scott Whitlock
źródło
Skąd Prezenter wie, co wyświetlić?
SonOfPirate
1
@ SonOfPirate - Zwykle odbywa się to za pośrednictwem mechanizmów WPF. Po Presenterprostu przykleja zwrócony ViewModelelement do drzewa wizualnego, a WPF chwyta odpowiedni View, łączy DataContexti umieszcza go w drzewie wizualnym. Możesz to zrobić za pomocą DataTemplates, które deklarują, jaki ViewModeltyp renderują, lub możesz utworzyć niestandardowy selektor szablonu danych. Jest to nadal w zakresie funkcji WPF.
Scott Whitlock