Инициализация модели

Что-то, что меня пугает MVVM, - если я использую первый подход к построению объектов (это, по-видимому, самый распространенный подход, по крайней мере после многократного чтения и поиска), как мне получить контекстуальную информацию в viewmodel?

Я видел много ответов на подобные вопросы: "используйте контейнер DI, чтобы ввести вашу модель", но это меня не помогает, поэтому я приведу небольшой пример.

Скажем, мое приложение является PeopleEditor. Он был создан для загрузки и редактирования объектов People, которые являются сложными. Когда вы загружаете приложение, вы получаете домашний экран, который загружает кучу людей в память - пусть говорят, что все они доступны через коллекцию, которую я могу получить из своего контейнера. Нажимая на Person, вы попадаете на экран редактора. Редактор является сложным, поэтому это не тривиальный просмотр мастер-детали, реализованный на одном экране.

Итак, на главном экране, когда я нажимаю человека, приложение должно создать новый view и viewmodel и показать представление. Если я сначала создаю viewmodel, через контейнер или нет, я могу инициализировать его соответствующим объектом person. Это кажется мне очень естественным, поэтому мне трудно понять, почему первый взгляд кажется преобладающим. Как мне это сделать, используя первый подход? Представление создало бы viewmodel, которая может попасть в коллекцию People, но не знает, кто ее редактирует. EDIT для ясности: Редакторы нескольких людей могут существовать одновременно, каждый из которых редактирует другого человека.

В эталонной реализации Prism 4.0 alpha MVVM используется "обработчик состояния", который в основном является сервисом, который приложение использует для хранения параметра конструктора в контейнере. Он сохраняет состояние и вызывает ShowView, и модель представления, которая в конечном итоге создается, импортирует объект состояния. Для меня это кажется неуклюжим - похоже, он пытается притворяться, что он слабо связан, когда это действительно не так. Есть ли у кого-нибудь другие рекомендации?

Ответ 1

nlawalker,

Я не эксперт, но я узнаю о View-First и Model-First:

  • View-First: просмотр программ ViewModel, вы создаете представление, а затем автоматически создаете viewmodel.
  • Model-First: ViewModel programs View, вы создаете граф объекта ViewModel в корневом приложении, присваиваете ему контекст данных корневого представления. затем позволяет визуализировать изображение, связанное с ним, зависит от модели представления.

Не означает, что подход Model-First плох, но я предпочитаю подход View-First, потому что viewmodel может сидеть в коде позади, поэтому, когда какой-то процесс требует необязательной дружественной задачи (PasswordBox, DialogConfirmation, ClosingForm и т.д.), Я могу написать свою логику в коде.

В любом случае, Чтобы решить этот случай, я обычно использовал комбинацию IOC и Event Aggregator. Вот он:

  • Для viewmodel требуется, чтобы контекстная информация регистрировала свой экземпляр в контейнере IOC, чем его тип. Таким образом, он не готов даже к его мнению.
  • При действии навигации (при нажатии на элемент списка людей) разрешите просмотр с помощью распознавателя контейнера IOC. и отправить событие на навигационную шину с указанным параметром. Далее это событие будет захватываться целевым ViewModel и что-то делать.

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

UPDATE

но затем, чтобы заполнить его каким-либо локальным контекстом, мне нужно использовать глобальное средство для отправки ему события?

В вашем случае контекстный объект не является локальным, а скорее представляет собой сообщение, переданное между вызовом объекта. Очевидно, что в вашем первом подходе к модели вы:

//selectedPeople is contextual object
myPeopleDetailVM.LoadData(selectedPeople)

он будет почти таким же, когда вы передаете selectedPeople аргументу шины событий.

Если вы рассматриваете производительность, то можете сравнить ее с WPF Routed Event System в этом случае стратегия маршрутизации сложнее, чем Event Bus и я думаю, если вы достаточно уверены в использовании WPF-маршрутизируемого события, а не с агрегированием событий.

Единственная проблема, я вижу, если вы используете встроенный агрегатор событий фрейма (prism, mvvmlight), ваша модель просмотра загрязнена с помощью шины событий, если вы жалуетесь на это, я соглашаюсь с вами.

Надеюсь, что это поможет.

Ответ 2

Если вы используете Prism, вы можете легко и аккуратно решить эту проблему, используя свою навигационную функцию. Используйте IRegionManager.RequestNavigate для перехода от основного представления к представлению редактирования путем создания целевого представления Uri для включения параметра строки запроса для соответствующего идентификатора Person. Вы можете извлечь этот идентификатор в модели метода представления OnNavigatedTo() целевого представления (член INavigationAware. Модель представления должна реализовать этот интерфейс).

Вы можете увидеть это в действии в примере приложения "View-Swithing Navigation", которое поставляется с загрузкой Prism. Его в папке Quickstarts.

Из того же примера приложения (которое имитирует Outlook), этот следующий код используется для перехода от InboxView к EmailView, чтобы открыть конкретный адрес электронной почты из почтового ящика:

var builder = new StringBuilder();
builder.Append(EmailViewKey);
var query = new UriQuery();
query.Add(EmailIdKey, document.Id.ToString("N"));
builder.Append(query);
this.regionManager.RequestNavigate(RegionNames.MainContentRegion, new Uri(builder.ToString(), UriKind.Relative));

И в модели представления EmailView EmailViewModel электронное письмо, которое нужно открыть, извлекается из контекста навигации следующим образом:

 void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
    {
        // todo: 15 - Orient to the right context
        //
        // When this view model is navigated to, it gathers the
        // requested EmailId from the navigation context parameters.
        //
        // It also captures the navigation Journal so it
        // can offer a 'go back' command.
        var emailId = GetRequestedEmailId(navigationContext);
        if (emailId.HasValue)
        {
            this.Email = this.emailService.GetEmailDocument(emailId.Value);
        }

        this.navigationJournal = navigationContext.NavigationService.Journal;
    }

 private Guid? GetRequestedEmailId(NavigationContext navigationContext)
    {
        var email = navigationContext.Parameters[EmailIdKey];
        Guid emailId;
        if (email != null && Guid.TryParse(email, out emailId))
        {
            return emailId;
        }

        return null;
    }