Кто должен управлять навигацией в приложении MVVM?

33

Пример # 1: у меня есть представление, отображаемое в моем приложении MVVM (давайте использовать Silverlight для целей обсуждения), и я нажимаю кнопку, которая должна перенести меня на новую страницу.

Пример # 2: В этом же представлении есть еще одна кнопка, которая при нажатии открывает окно подробностей в дочернем окне (диалоге).

Мы знаем, что будут объекты Command, представленные нашей ViewModel, привязанными к кнопкам с методами, которые реагируют на щелчок пользователя. Но что тогда? Как мы закончим действие? Даже если мы используем так называемый NavigationService, о чем мы говорим?

Чтобы быть более конкретным, в традиционной модели View-first (такой как схемы навигации на основе URL-адресов, такие как в Интернете или встроенной инфраструктуре навигации SL), объектам Command необходимо знать, какой View будет отображаться следующим. Это, кажется, пересекает черту, когда дело доходит до разделения проблем, продвигаемых шаблоном.

С другой стороны, если кнопка не связана с объектом Command и ведет себя как гиперссылка, правила разметки могут быть определены в разметке. Но хотим ли мы, чтобы представления управляли потоком приложений и не были ли навигация просто еще одним типом бизнес-логики? (Я могу сказать да в некоторых случаях и нет в других.)

Для меня утопическая реализация шаблона MVVM (и я слышал, что другие утверждают это) состояла бы в том, чтобы иметь ViewModel, подключенный таким образом, чтобы приложение могло работать без головы (то есть без Views). Это обеспечивает большую площадь поверхности для тестирования на основе кода и делает представления настоящей оболочкой для приложения. И моя ViewModel не должна заботиться, отображается ли она в главном окне, плавающей панели или дочернем окне, не так ли?

В соответствии с этим подходом во время выполнения это зависит от какого-то другого механизма, связывающего то, что View должно отображаться для каждой ViewModel. Но что, если мы хотим поделиться View с несколькими ViewModels или наоборот?

Таким образом, учитывая необходимость управлять отношениями View-ViewModel, чтобы мы знали, что отображать, когда наряду с необходимостью перемещаться между представлениями, включая отображение дочерних окон / диалогов, как мы действительно выполняем это в шаблоне MVVM?

SonOfPirate
источник

Ответы:

21

Навигация всегда должна обрабатываться в ViewModel.

Вы на правильном пути, думая, что идеальная реализация шаблона проектирования MVVM будет означать, что вы можете запустить свое приложение полностью без Views, и вы не сможете этого сделать, если ваши Views контролируют Navigation.

У меня обычно есть ApplicationViewModelили ShellViewModel, который обрабатывает общее состояние моего приложения. Это включает в себя CurrentPage(который является ViewModel) и код для обработки ChangePageEvents. (Он также часто используется для других объектов приложения, таких как CurrentUser или ErrorMessages)

Так что, если какая-либо ViewModel где-либо транслирует a ChangePageEvent(new SomePageViewModel), то ShellViewModelэто сообщение получит и переключится на ту CurrentPageстраницу, которая была указана в сообщении.

Я на самом деле написал сообщение в блоге о навигации с MVVM, если вам интересно

Рейчел
источник
2
Интересный подход. Четыре комментария: 1) Silverlight не поддерживает свойство DataType в DataTemplate, поэтому сопоставление DataTemplate с ViewModel невозможно в SL. 2) Это не относится ко многим-многим возможностям между Views и ViewModels. 3) Это не обрабатывает дочерние окна (или, по крайней мере, я не вижу, как). 4) Это требует тесной связи между вашей Application / Shell ViewModel и его детьми (внуками и т. Д.). Если у меня есть 40 страниц в моем приложении, эта ViewModel была бы слишком громоздкой в ​​управлении.
SonOfPirate
Тем не менее, это определенно что-то, чтобы рассмотреть.
SonOfPirate
@SonOfPirate 1) Silverlight не поддерживает неявное отображение DataTemplate (пока), но поддерживает - то DataTemplateSelector, что я обычно использую для приложений Silverlight. 2) Я использовал это во многих-многих ситуациях раньше. Например, одна ViewModel может иметь несколько представлений, или одно представление может быть связано с несколькими представлениями. Для примера первого см. Rachel53461.wordpress.com/2011/05/28/… . Для последующего вам просто нужно указать, что несколько ViewModels сопоставляются с одним и тем же представлением в вашем DataTemplateSelector.
Рэйчел
3) Это просто базовый пример. Если бы вы знали, что ваше приложение будет состоять из нескольких окон, вы, очевидно, изменили бы ShellViewModel для обработки нескольких окон CurrentPages4) Еще раз, это был лишь базовый пример. В действительности все мои PageViewModel основаны на каком-то базовом классе, поэтому моя ShellViewModel работает только с базовым классом или интерфейсом, таким как IPageViewModel. Действительно, самая большая грязная часть отображения - это DataTemplateSelector, который должен был бы отобразить 40 видов на 40 моделей представления.
Рэйчел
@SonOfPirate Я надеюсь, что ответил на некоторые ваши вопросы. Не стесняйтесь искать меня в чате, если у вас есть другие :)
Рэйчел
6

Ради закрытия я думал, что опубликую направление, которое я наконец выбрал, чтобы решить эту проблему.

Первым решением было использование встроенной в Silverlight инфраструктуры навигации по страницам. Это решение было основано на нескольких факторах, включая знание того, что этот тип навигации переносится Microsoft в приложения Windows 8 Metro и согласуется с навигацией в приложениях Phone 7.

Чтобы заставить его работать, я затем посмотрел на работу, которую ASP.NET MVC проделал с конвенциональной навигацией. Элемент управления Frame использует URI, чтобы найти «страницу» для отображения. Сходство дало возможность использовать похожий подход на основе соглашений в приложении Silverlight. Хитрость заключалась в том, чтобы заставить все это работать вместе в стиле MVVM.

Решением является NavigationService. Эта служба предоставляет несколько методов, таких как NavigateTo и Back, которые ViewModels может использовать для инициирования изменения страницы. Когда запрашивается новая страница, NavigationService отправляет сообщение CurrentPageChangedMessage с помощью функции MVVMLight Messenger.

Представление, содержащее элемент управления Frame, имеет свой собственный ViewModel, установленный как DataContext, который прослушивает это сообщение. Когда получено, имя нового представления передается через функцию отображения, которая применяет наши правила соглашения и устанавливается на свойство CurrentPage. Свойство Source элемента управления Frame связано со свойством CurrentPage. В результате установка свойства обновляет источник и запускает навигацию.

Возвращаясь к NavigationService. Метод NavigateTo принимает имя целевой страницы. Чтобы убедиться, что у ViewModel нет проблем с пользовательским интерфейсом, используемое имя - это имя ViewModel для отображения. Я на самом деле создал перечисление, которое имеет поле для каждой навигационной ViewModel в качестве помощника и для устранения магических строк во всем приложении. Функция отображения, о которой я упоминал выше, удалит суффикс «ViewModel» из имени, добавит «Page» к имени и задаст полное имя «Views {Name} Page.xaml».

Так, например, чтобы перейти к просмотру сведений о клиенте, я могу позвонить:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

Значением CustomerDetails является «CustomerDetailsViewModel», которое сопоставлено с «Views \ CustomerDetailsPage.xaml».

Прелесть этого подхода в том, что пользовательский интерфейс полностью отделен от ViewModels, но у нас есть полная поддержка навигации. Тем не менее, теперь я могу изменить свое приложение и в любое время, когда оно будет изменено.

Надеюсь, объяснение поможет.

SonOfPirate
источник
2

Подобно тому, что сказала Рэйчел, мое приложение, в основном MVVM, имеет функцию Presenterдля переключения между окнами или страницами. Рэйчел называет это ApplicationViewModel, но, по моему опыту, это обычно должно делать больше, чем просто цель привязки (например, получать сообщения, создавать Windows и т. Д.), Так что технически это больше похоже на традиционное Presenterили Controller.

В моем приложении мой Presenterначинается с CurrentViewModel. PresenterПерехватывает все коммуникации между Viewи ViewModel. Одна из вещей, которую ViewModelможно сделать во время взаимодействия, это вернуть новую ViewModel, что означает, что Windowдолжна отображаться новая страница или новая . The Presenterзаботится о создании или перезаписи Viewдля нового ViewModelи настройке DataContext.

Результатом действия также может быть то, что ViewModel«завершено», и в этом случае Presenterобнаруживает это и закрывает окно, или выталкивает это ViewModelиз стека виртуальных машин и возвращается к отображению предыдущей страницы.

Скотт Уитлок
источник
Как докладчик узнает, какой вид отображать?
SonOfPirate
1
@SonOfPirate - Обычно это делается с помощью механизмов WPF. Just Presenterпросто вставляет возвращаемое ViewModelв визуальное дерево, а WPF захватывает соответствующее View, подключает DataContextи помещает его в визуальное дерево. Вы можете сделать это, используя DataTemplates, которые объявляют, какой ViewModelтип они отображают, или вы можете создать собственный селектор шаблонов данных. Это все еще в сфере возможностей WPF.
Скотт Уитлок