Навигация с помощью MVVM
Когда я только начинал работать с MVVM, я не понимал, как перемещаться между страницами. Я твердо верю в использование ViewModels для всего (кроме кода, специфичного для View), и что пользовательский интерфейс - это просто удобный интерфейс для ваших ViewModels. Я не хотел создавать кнопку на странице, которая имеет какой-либо код программной части для переключения страниц, и мне не нравилась идея распределения моей навигации по всем моделям просмотра.
В конце концов я понял, что решение было простым: мне нужна была ViewModel для самого приложения, которая содержала бы состояние приложения, например CurrentPage.
Вот пример, основанный на простом примере MVVM .
ViewModel
Обычно я называю ViewModel ApplicationViewModel или ShellViewModel, но вы можете называть это как хотите. Это стартовая страница приложения, и обычно это единственный объект страницы или окна в моем проекте.
Обычно он содержит
List<ViewModelBase> PageViewModelsViewModelBase CurrentPageICommand ChangePageCommand |
Вот пример ApplicationViewModel, который я бы использовал для простого примера MVVM .
public class ApplicationViewModel : ObservableObject{ #region Fields private ICommand _changePageCommand; private IPageViewModel _currentPageViewModel; private List<IPageViewModel> _pageViewModels; #endregion public ApplicationViewModel() { // Add available pages PageViewModels.Add(new HomeViewModel()); PageViewModels.Add(new ProductsViewModel()); // Set starting page CurrentPageViewModel = PageViewModels[0]; } #region Properties / Commands public ICommand ChangePageCommand { get { if (_changePageCommand == null) { _changePageCommand = new RelayCommand( p => ChangeViewModel((IPageViewModel)p), p => p is IPageViewModel); } return _changePageCommand; } } public List<IPageViewModel> PageViewModels { get { if (_pageViewModels == null) _pageViewModels = new List<IPageViewModel>(); return _pageViewModels; } } public IPageViewModel CurrentPageViewModel { get { return _currentPageViewModel; } set { if (_currentPageViewModel != value) { _currentPageViewModel = value; OnPropertyChanged("CurrentPageViewModel"); } } } #endregion #region Methods private void ChangeViewModel(IPageViewModel viewModel) { if (!PageViewModels.Contains(viewModel)) PageViewModels.Add(viewModel); CurrentPageViewModel = PageViewModels .FirstOrDefault(vm => vm == viewModel); } #endregion} |
Это не скомпилируется сразу, потому что я внес в него некоторые изменения. Во-первых, все мои модели PageViewModel теперь наследуются от интерфейса IPageViewModel, поэтому они могут иметь некоторые общие свойства, такие как Name.
Я также создал новые модели HomeViewModel и HomeView, поскольку их сложно продемонстрировать, если у вас нет хотя бы двух страниц. HomeViewModel - это пустой класс, унаследованный от IPageViewModel, а HomeView - это просто пустой UserControl.
Кроме того, я добавил с до продукта сек ViewModel , так как он действительно имеет дело с несколькими продуктами, а не один.
Дополнительным преимуществом наличия ViewModel для управления состоянием приложения является то, что ее также можно использовать для обработки других объектов всего приложения, таких как текущий пользователь или сообщения об ошибках.
Вид
Мне также нужен ApplicationView для моей ApplicationViewModel. Он должен содержать какой-то вид навигации, который показывает список моделей PageViewModel, и щелчок по PageViewModel должен выполнять команду ChangePage.
Он также должен содержать элемент управления для отображения свойства CurrentPage, и я обычно использую для этого ContentControl. Это позволяет мне использовать DataTemplates, чтобы сообщить WPF, как рисовать каждую модель IPageViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 год 22 23 24 25 26 27 28 год 29 30 31 год 32 | <Window x:Class="SimpleMVVMExample.ApplicationView" xmlns:local="clr-namespace:SimpleMVVMExample" Title="Simple MVVM Example" Height="350" Width="525"> <Window.Resources> <DataTemplate DataType="{x:Type local:HomeViewModel}"> <local:HomeView /> </DataTemplate> <DataTemplate DataType="{x:Type local:ProductsViewModel}"> <local:ProductsView /> </DataTemplate> </Window.Resources> <DockPanel> <Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0"> <ItemsControl ItemsSource="{Binding PageViewModels}"> <ItemsControl.ItemTemplate> <DataTemplate> <Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding }" Margin="2,5"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Border> <ContentControl Content="{Binding CurrentPageViewModel}" /> </DockPanel></Window> |
В этом примере я использую ItemsControl для отображения моих PageViewModels. Каждый элемент рисуется с помощью Button, а свойство Command Button привязано к ChangePageCommand.
Поскольку DataContext кнопки является PageViewModel, я использовал привязку RelativeSource, чтобы найти ChangePageCommand. Я знаю, что мое окно - это ApplicationView, а DataContext - это ApplicationViewModel, поэтому эта привязка ищет VisualTree для тега Window и привязывается к Window.DataContext.ChangePageCommand.
Также обратите внимание, что я помещаю DataTemplates в Window.Resources, чтобы сообщить WPF, как рисовать каждую модель IPageViewModel. По умолчанию, если WPF встречает в своем визуальном дереве объект, который не знает, как обрабатывать, он будет рисовать его с помощью TextBlock, содержащего метод .ToString () объекта. Определяя DataTemplate, я говорю WPF использовать определенный шаблон вместо TextBlock по умолчанию.
Если вы продолжаете использовать простой пример MVVM, я переместил ProductView из ResourceDictionary в UserControl, чтобы упростить эту задачу.
Запуск примера
Последнее, что нужно сделать, это изменить App.xaml, чтобы запускать ApplicationView и ApplicationViewModel вместо ProductView / ProductViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 | public partial class App : Application{ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ApplicationView app = new ApplicationView(); ApplicationViewModel context = new ApplicationViewModel(); app.DataContext = context; app.Show(); }} |
Запустите проект, и вы должны увидеть что-то похожее на изображения ниже, которые быстро переключают CurrentPage при нажатии кнопок навигации.
Резюме
Вот и все. Простой пример навигации с MVVM.
Вы можете скачать исходный код этого образца здесь .
Как только вы освоитесь с WPF, я бы порекомендовал изучить использование системы обмена сообщениями, такой как Messenger MVVM Light или EventAggregator Microsoft Prism, для трансляции команд ChangePage из любой модели ViewModel, чтобы вам не нужно было искать ApplicationViewModel для выполнения ChangePageCommand, однако это в другой день.
<< Назад - Простой пример MVVM
>> Далее - Связь между моделями просмотра
Оцените это:
Эта запись была опубликована в воскресенье, 18 декабря 2011 г., в 15:57 и размещена в MVVM , WPF . Вы можете следить за любыми ответами на эту запись через канал RSS 2.0 . Вы можете оставить отзыв или откликнуться со своего сайта.


Я знаю, что прошло много времени с тех пор, как вы опубликовали это, но это все еще полезно. Спасибо, что поделился!
Отличная статья и очень полезная в джунглях интерпретаций концепции MVVM для навигации между различными представлениями. Я намерен использовать ваш пример кода в качестве стандартного шаблона для будущей разработки приложений с несколькими представлениями в соответствии с шаблоном MVVM.
Большое спасибо за вашу отличную работу!
С наилучшими пожеланиями
Другой инженер-программист
Эм… вау… Я выдергивала волосы, пытаясь понять это. Я только начал WPF и боролся с тем, как создать дизайн Master / Detail. Всякий раз, когда я щелкал по пункту меню, отображался только .ToString () модели представления. Таким образом, будет отображаться что-то вроде «ViewModels.ViewAViewModel» или что-то в этом роде. Я наткнулся на некоторые ваши ответы на SO и следил за этим блогом через них. Похоже, это богатство знаний. Спасибо, что нашли время сделать это. Это действительно помогает, и я очень ценю вашу тяжелую работу и усилия.
Привет Рэйчел,
Я нахожусь на своих первых шагах MVVM. Я следил за вашим руководством по навигации. У меня небольшой вопрос:
Как я могу интегрировать окно входа в систему в этот пример навигации, зная, что после входа пользователя в систему параметры навигации изменятся?
Спасибо за ваш ответ
Есть несколько способов сделать это. Вот один пример . Лучший способ выяснить это - просто попробовать, и если вы застряли, задайте вопрос о переполнении стека с вашим кодом, ожидаемым результатом и фактическим результатом.
Привет, Рэйчел. Отличная статья и блог. Два вопроса,
Можно ли избежать списка ViewModels внутри ApplicationViewModel? Потому что я думаю, что огромный список конкретных ViewModels станет проблемой для разработчика, если проект станет более сложным.
И вы до сих пор реализуете свои проекты таким образом?
Привет, Диос, к сожалению, моя текущая работа не заставляет меня работать с WPF, поэтому я мало использовал его в последние несколько лет. Да, есть способы обойти это, но я хотел, чтобы это было очень просто для цели этого сообщения в блоге. Если вы посмотрите на Google или Stack Overflow, я уверен, что вы найдете более сложные примеры
Спасибо за этот пост. Я мог придумать, как работать с общим подходом (первым). Это полезно. Удачи с будущими постами.
Не могли бы вы предоставить полный код или проект для навигации с помощью MVVM
Привет, Виджей, код должен быть связан в конце, если вы хотите загрузить копию
Очень хороший пост. Я просто наткнулся на ваш блог и хотел сказать, что мне очень понравилось просматривать ваши сообщения в блоге. В конце концов, я буду подписываться на вашу ленту и надеюсь, что вы скоро напишете снова!
Привет, Рэйчел!
Прежде всего, отличный гид. Я пытаюсь выучить C # и MVVM одновременно, и после того, что было похоже на сотни статей, именно ваша, наконец, заставила лампочки погаснуть. Спасибо за это.
Однако, у меня есть вопрос. Я хочу, чтобы на моей панели навигации были заголовки для групп связанных страниц. См. Для справки левую часть следующих изображений пользовательского интерфейса:

(Извините за размер текста на изображениях, надеюсь, вы уловили идею)
Я попытался посмотреть сообщение stackoverflow, в котором упоминаются составные коллекции, и я попытался взять его и применить к тому, что вы продемонстрировали, но не смог заставить его работать. Вот ссылка на сообщение для справки:
http://stackoverflow.com/questions/5473001/itemscontrol-with-multiple-datatemplates-for-a-viewmodel
Есть идеи, как бы вы реализовали это в своем формате MVVM? Мы будем очень благодарны за любое направление или помощь. Еще раз спасибо!
Спасибо, это очень помогло!
Большое тебе спасибо!
Спасибо за время и наглядный пример. Я выделил выбранную кнопку с помощью стиля при выборе (код ниже), но мне нужна помощь, чтобы кнопка оставалась выделенной, пока я работаю в выбранном UserControl.
Спасибо за любую помощь,
Питер
Привет, Питер, попробуйте опубликовать вопрос на stackoverflow.com с соответствующими фрагментами кода, и кто-то, вероятно, может вам помочь. Если нет, просто разместите ссылку с вопросом здесь, и я постараюсь взглянуть на нее, когда у меня будет время.
Рэйчел, можно ли каким-либо образом использовать ваш метод обновления виртуальной машины и вытащить конфигурации .xaml DataTemplate в App.xaml?
Кроме того, вы когда-нибудь писали что-нибудь об использовании службы обмена сообщениями?
Да, я написал « Связь между моделями представления с помощью MVVM», который представляет собой лишь базовый обзор систем обмена сообщениями, и « Упрощение» PRISM EventAggregator, который был вспомогательным классом, который было легко использовать для небольших проектов.
Я не понимаю, почему вы не могли этого сделать.
Если вам нужна помощь, я бы рекомендовал разместить вопрос на stackoverflow.com с соответствующими фрагментами кода, и на него, вероятно, быстро ответят. Если нет, просто пришлите мне ссылку, и я посмотрю, смогу ли я когда-нибудь взглянуть на нее.
У нас есть несколько приложений, которые работают в соответствии с вашим руководством, используя ViewModelLocator MVVM Light и функциональность Messenger MVVM Light. Прекрасно работает (спасибо за четкое объяснение).
Каждая ViewModel связана с одним View. Но каждый раз, когда пользователь переходит на страницу (View), создается новый экземпляр View (ViewModels - это одиночные модели, постоянно прослушивающие сообщения), а старый не собирается сборщиком мусора. Таким образом, память становится все больше и больше, а работа приложения замедляется. Как сделать так, чтобы представление собиралось сборщиком мусора после того, как оно было выгружено?
Я очень надеюсь, что ты сможешь мне помочь.
Привет Энн,
Я не большой поклонник ViewModelLocator, поэтому не уверен, что смогу вам помочь.
Мое лучшее предположение - дважды проверить все обработчики событий, чтобы убедиться, что ничто не удерживает View в памяти, или посмотреть, можете ли вы нарисовать View, используя DataTemplates, а не новые экземпляры. Это трудно сказать, имея мои ограниченные знания о ViewModelLocators и не видя вашего кода.
Я бы рекомендовал задать вопрос на StackOverflow с примерами кода, чтобы продемонстрировать проблему, и кто-то, вероятно, сможет вам в этом помочь. Не стесняйтесь размещать здесь ссылку на вопрос, и, возможно, я смогу взглянуть на нее.
Удачи!
Спасибо, мс Рэйчел, за этот простой и понятный урок! Я прошу вас об этом:
Каким будет подход, чтобы кнопка «Домашняя страница» также была * внутри * страницы продуктов (представьте, что она находится чуть ниже кнопки «получить продукт»), которая, конечно же, будет переходить на домашнюю страницу?
Спасибо, сохраните хорошую работу!
хмм, только что заметил последний абзац: «Я бы порекомендовал изучить использование системы обмена сообщениями, такой как Messenger MVVM Light или EventAggregator Microsoft Prism, для трансляции команд ChangePage из любой модели ViewModel»
поскольку я изучаю MVVM, я бы хотел избежать готовых фреймворков, чтобы я действительно мог понять, что происходит (а потом я смогу использовать фреймворки). Поэтому я был бы рад, если бы вы могли дать какие-либо советы по трансляции событий / команд такого типа без использования фреймворка. (* готовится к гугл-фу *)
Еще раз спасибо!
Сожалею, что никогда не удосужился создать свою собственную, поэтому не уверен, смогу ли я помочь направить вас в правильном направлении. Я знаю, что существует множество вариантов таких фреймворков обмена сообщениями, некоторые из них Microsoft и некоторые сторонние, хотя я использовал только Messenger MVVM Light, EventAggregator Prism и атрибуты [EventSubscription] и [EventPublication] CAB.
В этом случае я бы рекомендовал использовать систему трансляции событий, такую как Messenger от MVVM Light или EventAggregator от Prism. В качестве альтернативы ваш ApplicationViewModel может установить что-то вроде ProductViewModel.HomeCommandDelegate во время его создания
Сделал реблог на « Докторе Кто» и прокомментировал: «
Это отличная серия статей Рэйчел о шаблоне программирования MVVM. Стоит прочитать.
Очень хорошая статья о шаблоне проектирования MVVM и навигации в приложении WPF. Спасибо за публикацию!
У меня есть один вопрос; где определение интерфейса IPageViewModel?
Спасибо! И интерфейс IPageViewModel должен находиться в образце кода в той же папке, что и .csproj. Убедитесь, что у вас именно эта версия, а не версия из предыдущей статьи.
Спасибо!
Привет,
Я попытался добавить несколько просмотров. Кнопки отображаются, но когда я нажимаю на одну из них, я получаю только имя моего класса ViewModel? Как я могу связать мою модель VIewModel с правильным представлением? И почему работает на 3 из 4?
Заранее спасибо!
Привет, Франц,
Скорее всего, у вас нет DataTemplate, определяющего тип вашего объекта ViewModel, WPF визуализирует объект с использованием шаблона по умолчанию, который представляет собой просто текст, содержащий .ToString () объекта.
Если вы добавите свои шаблоны данных, они должны работать правильно.
Удачи!
Рэйчел
Привет, я абсолютный новичок в MVVM, и я хотел бы знать об отправке данных между UserControl. Оба УК находятся на одной странице. Что-то вроде «Список и детали». Я хотел бы щелкнуть элемент списка и показать результат в детали UserControl. Как мне это сделать? Вы могли бы дать мне совет или пример?
Привет Самамбайя,
В вашем примере в идеале у вас должна быть одна ViewModel как с коллекцией объектов, так и со свойством SelectedObject, и каждый UserControl будет привязан к ним. Если два объекта находятся в совершенно разных областях приложения и было бы трудно использовать одну и ту же родительскую объектную модель, я бы рекомендовал изучить систему событий WPF, такую как объект Messenger MVVM Light или EventAggregator Microsoft Prism.
Спасибо,
Рэйчел
Привет, Рэйчел!
Спасибо за показ этой полезной статьи, которая похожа на то, что я пытаюсь достичь в настоящее время: используйте группу переключателей (привязка команд) для переключения представлений с помощью MVVM. Однако мне не удалось заставить его работать. Можно ли проиллюстрировать своим примером кода, чтобы показать, как это можно сделать? Спасибо.
Привет, я,
Я бы предложил использовать ListBox с перезаписанным стилем для использования RadioButtons, чтобы он легко отслеживал выбранный элемент. Затем вы можете привязать ItemsSource к вашим AvailableScreens и привязать SelectedItem к любому выбранному в данный момент экрану.
Если вам нужна помощь с таким решением, попробуйте опубликовать вопрос с подробностями на http://www.stackoverflow.com и оставьте ссылку здесь для меня, и я посмотрю (при условии, что кто-то другой вам не поможет. вышел первым!).
Удачи с этим!
Привет Рэйчел,
Замечательный пост!
Есть ли простой способ заставить исчезнуть старый вид и исчезнуть новый вид?
Любое предложение будет высоко оценено
Вы, вероятно, захотите изучить WPF Animations и оживить свойство Opacity. Удачи!
Привет, к сожалению, код не может быть загружен по какой-то причине.
Я получаю примерно следующее сообщение: «Вы подключились к wired.meraki.com, но, скорее всего, в настоящее время не подключены к устройству Cisco Meraki».
Вы можете это проверить?
Спасибо!
Привет, Avots, я не уверен, почему не работает. Я только что перепроверил ссылку, и мне кажется, что все в порядке, может быть, это проблема с вашим интернет-соединением? Я не знаю, что такое Meraki, но похоже, что это что-то особенное для вашей сети.
отличная серия mvvm большое
спасибо!
Спасибо за отличный пример. Как можно сделать навигацию по представлению с использованием класса MVVM Light Messenger?
Привет, Вой,
Обычно мои ViewModel генерируют событие (Messenger.Default.Send) всякий раз, когда он хочет изменить текущий экран, а мой основной AppViewModel подписывается на получение этих сообщений и изменяет currentContent всякий раз, когда он его получает.
Итак, должно ли событие Send быть в конструкторах всех моих моделей просмотра и событие регистрации в конструкторе основной модели представления приложения? Или событие Register должно быть в ChangePageCommand?
Прошло некоторое время с тех пор, как я использовал MVVM Light, однако я считаю, что он использует одноэлементный экземпляр класса Messenger, поэтому, где бы вы ни захотели изменить страницы, вы должны называть что-то вроде Messenger.Default.Send (…) и передавать его как угодно аргументы событий, которые вы хотите. Событие Register необходимо подключить только к тому классу, который отвечает за фактическое изменение представления, например к модели представления основного приложения.
Привет, Рэйчел, отличные статьи, спасибо!
Один вопрос: я хотел удалить «боковую панель кнопок» из окна ApplicationView. Итак, я реализовал навигацию следующим образом: -
Вместо того, чтобы иметь коллекцию модели представления, при запуске приложения я устанавливаю контекст данных ApplicationWiew на новый экземпляр моего первого представления, передавая конструктору сам ApplicationViewModel:
CurrentPageViewModel = new HomeViewModel(this)поэтому я вставляю ApplicationViewModel в другую модель просмотра.
-Для выполнения навигации я создал команду, которая устанавливает CurrentPageViewModel на новый экземпляр другой модели просмотра:
_applicationViewModel.ChangeViewModel(new AboutViewModel());Это хороший способ выполнить навигацию в этом сценарии?
Спасибо!
Даниэле
Привет, Даниэле, я не вижу проблем с использованием этого подхода, если он работает с вашим стилем приложения.
Однако следует остерегаться нескольких вещей: убедиться, что для ваших ViewModel имеет смысл знать друг о друге таким образом, и это нормально - создавать новую ViewModel каждый раз, когда вызывается этот метод.
Я всегда был большим поклонником в первую очередь практичности, поэтому, если это работает для вас и практично для вашего фреймворка, дерзайте!
Рэйчел
Привет Рэйчел,
Я заметил, что вы добавляете модели просмотра страниц в свою ApplicationViewModel. Я вижу некоторые проблемы с этим:
1. ApplicationViewModel знает обо всех конкретных под-моделях представления.
2. Если суб-ViewModels имеют много свойств или содержат свои собственные sub-viewModels, тогда при запуске приложения может быть открыто много viewModels, что увеличивает использование ресурсов приложением.
Возьмем, к примеру, модульное тестирование. Если бы я тестировал ApplicationViewModel, весь граф объекта, включая HomeViewModel и ProductsViewModel, был бы в памяти. В реальном приложении у вас может быть более 10 страниц, каждая из которых может иметь свои собственные страницы (и так далее). Это кажется немного тяжелым, когда у меня должно быть максимум AppViewModel и любые имитируемые зависимости в памяти во время тестирования ApplicationViewModel.
Кроме того, что произойдет, если модели представления, используемые в качестве страниц, будут иметь свои собственные зависимости? Кто отвечает за их решение? и если модели просмотра страниц также имеют свой собственный набор моделей просмотра страниц, каждая со своими собственными зависимостями ...
Мне нравится простота и элегантность использования этого подхода, но при его использовании я столкнулся с проблемами, описанными выше. В настоящее время я не уверен, как лучше всего преодолеть эти препятствия.
Очевидно, это пример, поэтому я понимаю, почему в статье не рассматриваются эти вопросы, но мне было интересно, сталкивались ли вы с этими проблемами в любом из написанных вами приложений WPF, и если да, то какие решения вы бы порекомендовали.
Привет, Бенджамин,
Типичное решение, которое я вижу, - это использование NavigationService, который отвечает за создание и управление ViewModels и связанными с ними представлениями.
Обычно он создает ViewModel только тогда, когда он запрашивается для отображения в первый раз, и может обрабатывать другие случаи в зависимости от ваших требований, таких как управление памятью, ViewModels, выходящие за рамки, и т. Д.
Раньше я также обычно использовал систему событий, такую как PRISMs EventAggregator или класс Messenger MVVM Light, поэтому ViewModels не обязательно должны иметь прямую ссылку друг на друга.
Надеюсь, это поможет ответить на ваши вопросы!
Рэйчел
Привет Рэйчел,
Спасибо за прекрасно написанную статью, на которую я так рад, что наткнулся на нее. Я думаю, что это хорошая модель для приложения, которое я в настоящее время конвертирую в качестве учебного опыта из Delphi 7 / VCL в F # / WPF / MVVM.
Ваша статья помогла мне лучше понять эти новые технологии. Я почти уверен, что реорганизую приложение в его текущем состоянии, с одним представлением главного окна / виртуальной машиной и двумя (независимыми) вкладками, на несколько (в конечном итоге трех) представлений / виртуальных машин в соответствии с вашим дизайном, поскольку это намного лучше (от как с точки зрения кодирования, так и с точки зрения использования).
С уважением,
Франц
Привет, Рэйчел. Отличный пост в блоге! Однако у меня есть несколько вопросов:
1) Как бы вы справились с передачей параметров из одной модели просмотра в другую? Другими словами, команда запускается на ViewModel1, и приложение переключается на ViewModel2, но ViewModel2 нуждается в параметре из ViewModel1.
2) При создании экземпляра списка ViewModels, какой подход лучше всего подходит для ViewModels, которые полагаются на параметры из других ViewModels? Следует ли их добавлять в список с параметрами по умолчанию? Можно ли изменить параметры по умолчанию, когда ViewModel, от которого они зависят, передает им параметры?
Любая помощь высоко ценится!
Привет Майкл,
Обычно мы используем какую-либо систему обмена сообщениями для связи между ViewModels. Я бы порекомендовал Microsoft PRISM EventAggregator или MVVM Light Messenger, или, если вы действительно хотели, вы также могли бы вместо этого создать свой собственный.
Если вам интересно, у меня есть их краткий обзор: Связь между ViewModels с MVVM
Удачи с вашим проектом!
Рэйчел
Спасибо, Рэйчел! Я проверю ваши рекомендации.
Я заметил, что в вашем ApplicationViewModel у вас есть список ViewModel, который состоит из нового экземпляра каждой ViewModel. Может ли этот метод привести к проблемам с производительностью или это стандартный способ?
Спасибо
Я уверен, что это возможно, и вы могли бы лениво загружать ViewModels по мере необходимости вместо того, чтобы создавать их все изначально. Это зависит от размера ваших ViewModels.
Это отличная статья, спасибо.
Как это приложение получает правильный вид ViewModel? Где указано, что HomeViewModel связана с HomeView?
Я добавил, например, другую ViewModel в список PageViewModels, и он автоматически добавляет кнопку с правильным недавно созданным пользовательским View ... Я просто не понимаю, как это возможно? Я никогда не заявлял, что эта новая ViewModel связана с новым View.
Command = ”{Binding DataContext.ChangePageCommand, RelativeSource = {RelativeSource AncestorType = {x: Type Window}}}”
CommandParameter = ”{Binding}”
Это из-за этого? Что делает Relative AncestorType = {x: Type Windows}? И почему CommandParameter ни к чему не привязан?
C # и MVVM выглядят действительно забавным способом программирования, но для меня это довольно абстрактная техника ...
Привет Дено,
ContentControl имеет свойство Content, привязанное к CurrentPageViewModel. По умолчанию WPF не знает, как рисовать ViewModel, однако я определил DataTemplate в .Resources приложения, чтобы сообщить WPF, что он должен рисовать что-либо типа HomeViewModel с HomeView и ProductsViewModel с ProductsView.
Привязка Command только связывает событие нажатия кнопки с ChangePageCommand в ViewModel. Из-за способа наследования DataContext за элементами управления пользовательского интерфейса нам нужно использовать привязку RelativeSource, чтобы указать ему искать в Window.DataContext для ChangePageCommand. Пустая привязка - это просто ярлык для ссылки на текущий DataContext, поэтому он передает выбранную модель IPageViewModel этой команде.
Если вы новичок в WPF, я бы посоветовал прочитать мои статьи « Понимание изменения мышления при переходе от Winforms к WPF» и « Что это за« DataContext », о котором вы говорите? . Я думаю, они могут помочь ответить на некоторые ваши вопросы, связанные с привязками, DataContext и MVVM.
Вау, спасибо за быстрый ответ ... и этот блог содержит очень полезную информацию, я не знал об этой огромной базовой статье WPF. Я значительно улучшился с тех пор, как задал этот вопрос, но я все еще новичок, поэтому я все еще собираюсь изучить это ... спасибо!
Привет Рэйчел,
В статье вы упомянули, что «DataContext кнопки является PageViewModel, поэтому вы использовали привязку RelativeSource для поиска ChangePageCommand».
Я изо всех сил пытаюсь понять, почему DataContext кнопки является PageViewModel? Не могли бы вы пролить свет на это.
Я прочитал вашу статью, объясняющую DataContext, из которой я могу сделать вывод, что если DataContext не установлен для элемента, он наследует его от родительского элемента. Здесь я вижу, что DataContext не указан ни для одного элемента, кроме элемента Window (указанного в App.xaml.cs), DataContext которого является ApplicationViewModel. Итак, для меня Datacontext кнопки должен быть ApplicationViewModel.
Что я здесь неправильно думаю?
Привет, Раджеш, чтобы понять здесь наследование DataContext, вам нужно понять, как работает ItemsControl.
ItemsControl возьмет каждый элемент в ItemsSource, создаст копию объектов пользовательского интерфейса в ItemsPanelTemplate и назначит DataContext этих объектов пользовательского интерфейса, равный элементу в ItemsSource. Вы можете узнать больше о ItemsControl здесь, если вам интересно.
Итак, в этом случае ItemsControl привязан к коллекции PageViewModels, поэтому он создаст экземпляр ItemsPanelTemplate для каждой PageViewModel и назначит DataContext, равный PageViewModels [n] для каждой из них.
Надеюсь, это поможет,
Рэйчел
Спасибо за этот пример,
Я никогда раньше не использовал свою виртуальную машину для навигации, и у меня есть вопрос по ApplicationView.
Мне нравится использовать (изменить стиль) радиокнопки для переключения представлений. Таким образом, пользователь может видеть, какой вид выбран. Как насчет установки правильного радиокнопки на IsChecked при изменении представления в модели представления.
Уолтер
Привет, Уолтер,
Обычно для чего-то подобного, где я хочу отслеживать SelectedItem, я бы использовал ListBox, стилизованный под использование RadioButtons. Вы можете найти здесь пример .
Удачи с этим,
Рэйчел
Привет Рэйчел,
Большое спасибо. Это именно то, что я искал на этой неделе!
Теперь я хотел бы использовать встроенный элемент управления Ribbon для загрузки моих моделей просмотра.
Я пытался изменить ваш источник, чтобы сделать это, но моя кнопка ленты всегда отключена ???
Привет, Фарид, возможно, ты захочешь задать вопрос об этом на StackOverflow вместе с небольшим образцом исходного кода.
Раньше я не работал с элементом управления Ribbon и не очень помог бы, если бы не увидел ваш код.
Спасибо,
Рэйчел
Спасибо!
Привет, Рэйчел,
я (новичок в WPF) работаю над приложением на основе навигации, где у меня около 10 просмотров. Я пытаюсь понять, почему мы не используем NavigationService, встроенный в WPF.
Я был бы очень признателен, если бы вы могли рассказать мне или указать на ресурс по этой теме.
Спасибо,
Навид
Привет, Навид,
Честно говоря, раньше я не пользовался NavigationService. Я не могу вспомнить своих точных причин для этого, но я считаю, что это было потому, что я думал, что он слишком сильно смешивал слои данных и пользовательского интерфейса.
Мне всегда было проще позволить моему приложению (ViewModels) управлять потоком приложения и полностью исключить пользовательский интерфейс из уровней приложения.
Для меня слой пользовательского интерфейса - это просто визуальное представление уровня моего приложения, и мне не нравилось, когда мой уровень приложения беспокоился о том, как рисовать каждую страницу.
Привет!
Мой вопрос связан со следующей статьей, в которой вы разместили свой ответ.
Ссылка: http://stackoverflow.com/questions/7446825/open-child-window-inside-a-parent-window-in-wpf-using-mvvm
Я использую структуру MVVM и технику, которую вы упомянули в своем следующем сообщении.
https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Это тот же метод, который я использую для реализации своего приложения WPF. Я создаю новое приложение WPF и хочу использовать чистый MVVM, т.е. без кода.
Теперь я хочу открыть диалоговое окно или дочернее окно поверх окна приложения, нажав кнопку в текущем окне приложения. В этом окне я хочу показать некоторые данные в сетке данных и предоставить возможность хранить эти данные в сетке для поиска определенных данных в сетке, а затем хочу предоставить пользователю функциональность, в которой пользователь может выбрать конкретную строку из сетки и нажать «ОК» или «Отменить».
Теперь моя проблема: как я могу открыть это окно поверх окна приложения? Как я могу узнать, какую запись выбрал пользователь? и как только пользователь нажимает «ОК» или «Отмена», я хочу, чтобы это окно закрылось и выполнило некоторую операцию на основе выбора.
У вас есть образец для этого сценария? Или вы могли бы дать какой-нибудь образец для этого сценария?
Я могу поделиться с вами своим решением, если хотите.
Заранее спасибо.
Митеш.
Привет, Митеш,
Для меня это обычно зависит от того, какой это диалог.
Если это что-то вроде диалогового окна «Файл / Папка», я обычно обращаюсь к нему со слоя ViewModel.
Если это что-то вроде всплывающего окна, содержащего дочерний контент, я обычно использую настраиваемый элемент управления Popup Control в моем слое пользовательского интерфейса и управляю такими свойствами, как IsPopupVisible и PopupContent, из слоя ViewModel.
И третье решение - иметь класс, который единолично отвечает за управление диалогами, и использовать систему обмена сообщениями, чтобы запускать, когда появляются диалоги и какой контент они должны содержать.
В вашем примере я бы, вероятно, выбрал второй вариант, когда ViewModel будет содержать содержимое диалогового окна (включая SaveCommand и CancelCommand), а XAML будет написан за такое время, чтобы он обеспечивал визуальное представление данных вашего диалогового окна, когда это необходимо. .
Спасибо за эту статью, она мне очень помогла!
Я пытаюсь понять, почему, когда я переключаю страницы, предыдущая страница продолжает работать, потому что, когда я добавил MessageBox к фоновому работнику на моей странице обзора, он продолжал вызывать даже на других страницах, а когда я снова щелкнул Обзор, он просто добавил еще один Обзор потому что теперь я получал 2 уведомления, увеличивающихся при нажатии кнопки «Обзор» в каждом цикле, и так далее.
Кто-нибудь сталкивался с этой проблемой?
Привет, Адам,
Убедитесь, что вы переходите на существующую ViewModel, а не создаете новую каждый раз для переключения на страницу. И не забывайте, что изменение страниц вообще не удаляет и не удаляет старую страницу - она просто меняет активную страницу.
Если у вас возникли проблемы с кодом, я бы посоветовал вам опубликовать вопрос на stackoverflow.com и включить соответствующие части вашего кода. Вы получите ответ довольно быстро, или вы можете отправить мне ссылку на свой вопрос по электронной почте, если хотите.
Удачи с этим =)
Здравствуйте,
я использую ваш пример и хочу знать, как передать параметр в свой пользовательский элемент управления? В моем приложении слева будет меню (скорее всего, TreeView), и когда пользователь щелкает элемент в контекстном меню дерева, я загружаю пользовательский элемент управления с правой стороны. Пользовательскому элементу управления потребуется передать ему идентификатор или несколько параметров, чтобы он мог заполнить его View.
Буду признателен, если вы дадите мне знать, как это реализовать.
Спасибо.
Алекс
У меня был бы объект Model, содержащий данные для вашего UserControl, в вашей ViewModel, и я просто установил бы DataContext как объект в вашей ViewModel.
Трудно публиковать много кода в этих комментариях, но в идеале я бы хотел, чтобы моя ViewModel содержала ObservableCollection классов MyObject для TreeView.ItemsSource для привязки и свойство SelectedObject типа MyObject. Тогда я бы попросил UserControl просто привязать его DataContext к SelectedObject.
Спасибо за ответ. Я до сих пор не понимаю, как это сделать. Можно ли указать мне пример, в котором событие элемента TreeView будет переключаться между пользовательскими элементами управления и может передавать параметр пользовательскому элементу управления, чтобы он мог быть заполнен?
Спасибо
алекс
Лучший пример, который я могу найти, - это мой ответ на StackOverflow . Он использует объекты страницы, однако вы можете сделать что-то подобное со своими объектами.
Если у вас есть дополнительные вопросы, я бы рекомендовал разместить их на этом сайте вместе с коротким примером кода, демонстрирующим вашу проблему. Они довольно хорошо умеют помочь вам быстро решить вашу проблему.
Хм, если вы добавите меню и привяжете команду к MenuItem аналогичным образом, MenuItem будет отключен, хотя метод CanExecute всегда возвращает true. Не могу понять почему.
Привет, Ариштат,
Возможно, вы могли бы задать вопрос на StackOverflow с образцом кода, описывающим проблему, и прислать мне ссылку. Трудно сказать, в чем проблема, не видя кода, однако общая проблема, которую я вижу с меню, заключается в том, что они иногда не являются частью одного и того же визуального дерева, поэтому DataContext - это не то, что вы ожидаете.
Хороший пост, Рэйчел. При использовании этого я сталкиваюсь с проблемой (или, возможно, это связано с дизайном) - каждый раз, когда я переключаюсь на другую модель просмотра, создается новый экземпляр VIEW.
Связывание с mainview xaml -
Когда я переключаюсь взад и вперед, чтобы активировать ActiveViewModel - X, повторно создается представление, связанное с X (я могу проверить это, увидев, что конструктор представления попадает в отладчик).
Это приводит к тонким ошибкам, поскольку в моем представлении есть DataGrid, и если пользователь изменил какой-либо фильтр, группы - при повторном создании экземпляра представления все эти изменения исчезнут.
Примечание. Мои данные модели просмотра сохраняются каждый раз, поэтому у меня нет такой же проблемы, как у других людей, указанных выше.
Привет, Амит,
По умолчанию WPF выгружает объекты, которые не видны, поэтому похоже, что ваш UserControl выгружается, когда он скрыт, затем загружается его новая копия, когда вы переключаетесь обратно, что вызывает все изменения, внесенные в свойства, которые не привязаны к чему-то в DataContext, который нужно сбросить.
Что я обычно делаю, чтобы избежать такого поведения, так это повторное использование настраиваемого элемента управления TabControl, который сохраняет ContentPresenter каждого элемента TabItem при переключении вкладок и перезагружает тот же ContentPresenter при обратном переключении. Это сохраняет состояние элемента управления, когда он не виден, и перезагружает его так же, как и при переходе от него.
Пример кода и того, как он работает, можно найти в этом моем ответе на StackOverflow . Возможно, я напишу это решение в качестве следующего сообщения в блоге, так как я думаю, что исходный сайт, который я понял, и большая часть кода, больше не существует.
Удачи в твоем проекте,
Рэйчел
Привет, Рэйчел,
я новичок в WPF и MVVC. Последовал вашему примеру, но возникла небольшая проблема. Как только я покидаю одно представление и снова возвращаюсь к нему, конструкторы ViewModel для этого представления вызываются и теряют свое состояние. Почему это так и как этого избежать?
Привет, Киссак,
WPF выгружает ресурсы, которые не используются, включая невидимые объекты пользовательского интерфейса, поэтому похоже, что ваше представление инициализирует новую модель представления в качестве своего контекста данных при загрузке представления, что обычно не является тем, что я рекомендую.
Проверьте конструктор вашего представления, событие Loaded или XAML и посмотрите, не устанавливается ли DataContext где-нибудь в новый экземпляр вашей модели ViewModel.
Привет, Рэйчел, представление было одним из более старых экспериментов, в котором контекст данных устанавливался в XAML, поэтому ViewModel был воссоздан. Когда я его удалил, состояние сохраняется. Спасибо за уделенное время…
Привет, у меня такая же проблема, не могли бы вы ее объяснить? Я не знаю, как настроить DataContext, чтобы не терять состояние. Спасибо.
Привет, Лора,
Если ваши текущие привязанные элементы управления, похоже, теряют свое состояние, то вы, вероятно, сбрасываете DataContext, к которому они привязаны, например, вручную устанавливаете DataContext в коде ваших элементов управления.
Обычно вы не хотите когда-либо устанавливать DataContext из кода, стоящего за вашими UserControls, поскольку вы должны передавать DataContext в свой UserControl во время его использования, а его явная установка в коде программной части предотвратит это.
Если вы изо всех сил пытаетесь понять DataContext, я бы порекомендовал прочитать недавнее сообщение в блоге, которое я написал: Что это за «DataContext», о котором вы говорите?
Удачи с этим,
Рэйчел
Спасибо, Рэйчел!
Хороший пример. Работа над дизайном торговой точки, где начальным экраном является вход с клавиатуры. Итак, в этом примере я пытаюсь понять, как начать работу с представлением входа в систему. Как только пользователь войдет в систему, перейдите на домашнюю страницу ???
Раньше, когда я делал экран входа в систему, я показывал и проверял вход в систему еще до того, как основное приложение запускалось в методе OnStartup файла app.xaml.cs.
Надеюсь, это поможет =)
Хорошо, имеет смысл. Вопрос, по вашему предложению я добавил диалоговое окно журнала, которое появляется при запуске. Однако после входа в систему приложение просто закрывается (ShellView никогда не появляется). Что-нибудь пришло в голову?
Установите для Application.ShutdownMode значение, отличное от значения по умолчанию OnLastWindowClose. Я обычно использую OnExplicitShutdown
Привет, Рэйчел
Что я должен передать в мою функцию входа в систему (окно logindialog), чтобы закрыть его, а затем открыть ShellView, пробовал разные решения, и ничего не случилось, чтобы открыть ShellView ...
Я даже добавил «Application.ShutdownMode: как вы писали ранее.
Заранее спасибо.
Привет, Хэл, если вы используете ShowDialog для отображения окна входа в систему, тогда код в вашем OnStartup должен возобновиться, как только ваш диалог входа в систему вернет DialogResult
Хороший пример,
Пробовал использовать это в своем приложении, потому что я не хотел использовать Prism для этого небольшого приложения.
Единственное, что я хочу изменить, это вместо того, чтобы помещать все кнопки в shellview / applicationview, я просто хочу зарегистрировать представления в shellview. Затем добавление кнопок в разных представлениях, которые устанавливают для IPageViewModel CurrentPageViewModel в shellview соответствующее представление.
Но моя проблема в том, что я не могу получить доступ к List PageViewModels из моих моделей просмотра и установить CurrentPageViewModel оттуда.
Как я могу это решить?
Привет, Нилоу,
Обычно я использую какую-то систему событий для связи между ViewModels. Обычно моя ShellViewModel прослушивает что-то вроде ChangePageEvents, и любая кнопка, которая должна изменять страницу, просто транслирует сообщение ChangePageEvent, которое будет подхвачено и выполнено ShellViewModel.
Я написал краткий обзор того, что такое системы событий и как они работают, по ссылке выше , хотя когда-нибудь я надеюсь написать более полное руководство по их использованию.
Рэйчел,
спасибо за отличные примеры. Вы объясняете вещи, не предполагая, что аудитория много знает. Мне особенно понравился ваш комментарий о том, что ViewModels - это приложение, забудьте о представлениях, они просто отвлекают.
Желаю, чтобы вы сделали этот пример с помощью MVVM Light, но я разберусь с этим. Поверьте, основное отличие состоит в том, что логика, которую вы поместили в ApplicationViewModel, должна находиться в ViewModelLocator, который загружается при запуске приложения в MVVM Light.
Я разобрался. Решение заключалось в создании ShellVM верхнего уровня, которая обрабатывала бы навигацию так же, как ваша ApplicationVM. Попытка поместить логику навигации в VMLocator была ошибкой.
Рад, что вы разобрались. На самом деле я не использую ViewModelLocator, если могу, потому что мне кажется, что это немного ограничивает, и у меня были некоторые проблемы с ним в прошлом.
Спасибо за отличную статью.
Я подумал, что использование DataTemplate для определения того, какое представление использовать для каждой модели представления, было действительно умным!
Это действительно помогает отделить представление от ViewModel и означает, что другое окно может использовать другое представление для той же ViewModel без каких-либо изменений ViewModel.
У вас есть идеи о том, как WPF управляет временем жизни каждого представления? Например, если бы у меня было большое количество представлений, между которыми нужно было переключаться, как я мог бы избавиться от представлений, которые больше не используются?
Привет, Бенджамин,
WPF автоматически выгружает представление, когда оно больше не отображается. Например, если у вас есть TabControl с ViewA на одной вкладке и ViewB на другой вкладке, то переключение с вкладки ViewA на вкладку ViewB автоматически выгружает ViewA и загружает ViewB.
Привет, Рэйчел, спасибо за ответ.
Применимо ли ваш комментарий выше ко всем представлениям WPF или специфичен для TabControl?
Например, в вашем примере кода нажатие кнопки «Домой» полностью избавляет от пользовательского элемента управления продуктами?
Известны ли вам ситуации, когда такое автоматическое поведение могло вызвать неожиданные утечки памяти?
Он должен применяться ко всем элементам управления, а не только к TabControl. WPF будет загружать только те элементы управления, которые в данный момент видны на экране.
Я использовал WPF Inspector, чтобы проверить это ранее, и оказалось, что визуальное дерево действительно меняется, когда я переключаюсь между домашней страницей и страницей продуктов.
Еще раз спасибо за ваш ответ, он был очень оценен. Я с нетерпением жду вашего следующего сообщения в блоге.
Привет Рэйчел,
Спасибо за ваш пример навигации.
Я новичок в WPF и MVVM, учусь.
Я последовал твоему примеру, и все работает нормально.
В моем главном окне у меня есть четыре кнопки для вызова различных пользовательских элементов управления, таких как домашняя страница, продукты, устройства и список устройств.
В «Список устройств» у меня есть кнопка. Когда я нажимаю кнопку, список должен быть заполнен данными.
Моя проблема: когда я нажимаю кнопку, ничего не происходит, но если я нажимаю кнопку для вызова, например, домашней страницы, а затем нажимаю кнопку, чтобы снова вызвать «Список устройств», теперь я вижу список со всей информацией, которую я читать из базы данных.
Вы хоть представляете эту проблему?
Заранее спасибо за вашу помощь
Рикардо
Привет, Рикардо,
Трудно сказать, в чем проблема, не видя некоторого кода, однако я предполагаю, что кнопка находится в DeviceView, поэтому событие щелчка должно обрабатываться в DeviceViewModel через RelayCommand.
Возможно, вы можете задать вопрос на http://www.stackoverflow.com, описав вашу проблему, вместе с примерами кода, и кто-нибудь сможет вам помочь.
Гудлак с этим,
Рэйчел
Спасибо за простую для понимания, но очень мощную и полезную статью. Я использую этот метод в своем текущем проекте. Работает очень хорошо. Единственное изменение, которое я сделал, - это проверить, является ли модель представления, которую вы хотите изменить, той, на которой вы сейчас находитесь, я не меняю модель представления.
Привет, Марк, рад, что статья тебе понравилась!
Определенно измените класс оболочки в соответствии с вашими потребностями и не придерживайтесь того, что у меня есть! В любом случае это не совсем конкретный класс - это скорее абстрактная идея, которую вы можете изменить, чтобы она соответствовала стилю навигации любого проекта, над которым вы работаете. Обычно это различно для каждого проекта, над которым я работаю, однако все они работают с какой-либо моделью Application или Shell ViewModel, которая имеет аналогичную концепцию.
Рэйчел,
Мне очень нравится ваш подход, это определенно способ решить текущую проблему с навигацией. В одном из своих постов о stackoverflow, связанных с навигацией, вы рекомендовали избавиться от определения DataContext в UserControl:
http://stackoverflow.com/questions/8677500/wpf-mvvm-load-an-usercontrol-at-runtime
Как я могу таким образом решить проблему «смешиваемости» (пример данных, видимых в Expression Blend) пользовательских элементов управления? В настоящее время я следую методу, описанному в наборе инструментов MVVM Light. Имея класс Locator, с помощью Unity IoC он отделяет данные времени разработки от данных времени выполнения, поэтому Expression отображает образцы данных в моих пользовательских элементах управления.
Как только я переключусь на ContentControl и DataTemplate, у меня больше не будет данных дизайна, так как DataContext, который я сейчас использую, нарушит предложенный вами подход. Не могли бы вы помочь мне с идеей, связанной с этим вопросом? Я все еще хочу, чтобы мои пользовательские элементы управления могли смешиваться.
Большое спасибо,
Раду
Привет Раду,
Я не часто использую Expression Blend, но считаю, что вы можете использовать префикс d: для указания DataContext во время разработки. Это позволит вашим UserControls иметь DataContext в конструкторе, но не жестко закодировать DataContext в ваш UserControl.
<UserControlmc:Ignorable="d"d:DataContext="{Binding DesignTimeDataContext}"... >Я не уверен, но я считаю, что расширения разметки времени разработки были добавлены в VS2010, поэтому я думаю, что ViewModelLocators часто использовались до этого для решения проблемы смешиваемости, которую вы описываете. Лично мне вообще не нравится использовать ViewModelLocators (некоторые из причин можно найти здесь )
Удачи в твоем проекте,
Рэйчел
Очень хорошо написанная статья, Рэйчел, мне очень помогла навигация в MVVM.
В коде, показанном выше для ApplicationViewModel, есть список для хранения моделей представления, я мог ошибаться, но ни в одном из объявлений не указан общий тип, если это не что-то вроде:
частный список _pageViewModels;
Если это так, я думаю, что собственность должна быть изменена, чтобы последовать ее примеру.
Спасибо :)!
Спасибо, Боб, ты прав. Когда я копировал / вставлял код, он избавлялся от символов> и <и всего, что между ними. Я исправлю это.
Боже, вы все еще используете Windows XP? Когда вы планируете перейти на Win 7 (и Win 8)?
lol У меня на работе Windows 7, но на обоих моих домашних компьютерах стоит XP. Они созданы для игр, поэтому немного дороже, и мне пока действительно не приходилось их заменять.