WPF Prism — В чем смысл использования областей Prism?

#wpf #design-patterns #prism #prism-4

#wpf #шаблоны проектирования #призма #призма-4

Вопрос:

Мне просто интересно, в чем смысл регионов. Наверное, я не понимаю проблему, которую они решают.

Например, я вижу, что многие люди используют регионы для региона навигации, но тогда почему бы просто не привязать ItemsControl к ObservableCollection вместо того, чтобы иметь регион и загружать в разные элементы навигации в этот регион?


Реальный пример его использования / преимуществ по сравнению с альтернативами будет потрясающим!

Комментарии:

1. Мне пришлось перечитать вопрос, но я предполагаю, что вы не о #region том. Я собирался написать ответ, в котором говорилось: «абсолютно нет смысла — компилятор игнорирует их, а некомпетентные программисты используют их вместо рефакторинга»

2. @Merlyn Из контекста Prism это очевидно, нет?

3. @chibacity: это очевидно для любого, кто использовал Prism и знает, что такое область Prism. Но он ни в коем случае не говорит «Область призмы», он просто говорит «регион». Возможность заменить регион элементом ItemsControl устраняет двусмысленность для людей, которые знают WPF, а теги почти решают проблему, но мне пришлось гуглить, чтобы убедиться.

4. @Merlyn В названии написано Prism. Вы прочитали название?

5. @chibacity: кто-то отредактировал заголовок. Возможно, я пропустил редактирование, если оно произошло до моего комментария, когда я направлялся к двери. Я согласен, что теперь все ясно

Ответ №1:

Области позволяют вам определить область в вашей программе, которая существует для определенной цели. Так, например, у вас может быть область меню или область нижнего колонтитула. Затем вы можете выделить Views / ViewModels для этих конкретных регионов в отдельный раздел программы.

Таким образом, вместо того ApplicationViewModel , чтобы содержать свойства для MenuViewModel и FooterViewModel и привязывать представление к каждому разделу к этим свойствам, у вас будут отдельные ViewModels для меню и нижнего колонтитула, а ваша ApplicationViewModel будет иметь дело только с содержимым. Это лучший способ разделить некоторые логические границы в вашем приложении.

Лично я считаю, что регионы ужасно злоупотребляют и злоупотребляют. Я почти никогда не использую их, если у меня нет чего-то вроде верхнего или нижнего колонтитула, которые не связаны с остальной частью моего кода приложения. Они также в основном используются при разработке с использованием View-First, и я предпочитаю ViewModel-First.

Ответ №2:

Сравните RegionManager с EventAggregator, и вы увидите его преимущество…

EventAggregator позволяет различным компонентам публиковать / подписываться на события, не будучи связанными друг с другом. То же самое относится и к RegionManager… вы можете загрузить представление в область так, чтобы другое ваше представление не знало, что там происходит. Это отделяет ваши представления друг от друга… это не значит, что ВСЕ представления должны не знать друг о друге… бывают случаи, когда представление должно знать о другом представлении.


Посмотрите на Microsoft Outlook (примечание: я здесь все придумываю, включая имена, поскольку Outlook написан не на WPF, а на C ):

Основной пользовательский интерфейс будет иметь следующие регионы:

  • MenuRegion
  • NavigationRegion
  • ContentRegion
  • SideRegion

Области определяются в стандартных элементах управления (поэтому вам все равно нужны стандартные элементы управления), в частности ItemsControl, ContentControl и Selector из коробки (вы можете расширить другие элементы управления, чтобы они были «поддерживающими регион»). Они позволяют другому разделу кода управлять регионами путем разрешения и загрузки соответствующих представлений в эти регионы. В основном, чтобы все было разделено.

Вашему основному пользовательскому интерфейсу не нужно знать все о вашем приложении; вместо этого ему просто нужно знать, что у него есть меню, навигация, содержимое и боковая область. Какие представления, которые на самом деле размещены в регионах, не имеют значения. Это не означает, что все представления должны быть отделены друг от друга. Я вернусь к этому позже.

Итак, как это на самом деле развязывается? Вот сценарий: нажмите на значок календаря в элементе управления навигацией. Итак, что должно произойти, когда вы это сделаете?

  1. NavigationView — Кнопка (значок) привязана к an ICommand , поэтому она вызывает ExecuteLoadCalendar() функцию.
  2. NavigationViewModel ExecuteLoadCalendar() Функция использует EventAggregator , чтобы объявить, что пользователь пытается запустить календарь.
  3. ContentController ContentController Подписался на LoadCalendarAggregateEvent , так что выполняется. Здесь он разрешает / активирует CalendarView (СВЯЗАННЫЙ), используя IRegionManager и имя региона. Это должно быть сделано путем захвата ICalendarView вместо a CalendarView .

На протяжении всего процесса каждая часть отделяется, за исключением ContentController и CalendarView / ICalendarView . Конечно, вы могли бы сказать, что NavigationView / NavigationViewModel вроде как знал о CalendarView / CalendarViewModel , имея ICommand функцию and . Ну, «вроде» — это не то же самое, что «они делают», потому что код кода и код модели представления никогда не должны ссылаться на фактические CalendarView / CalendarViewModel объекты.

Кроме того, мы можем удалить «вид», сделав выполнение универсальным. Вместо того, чтобы иметь ExecuteLoadCalendar() функцию, она может иметь LoadContent(NavigationItem item) функцию, в которой AggregateEvent полезная нагрузка представляет собой некоторую идентификацию, например item.Name (String) (загруженную из базы данных, XML и т. Д.), Чтобы указать, что они нажали «Календарь». ContentController Использует те же данные для разрешения «Календаря» вместо ICalendarView (потому что на самом деле не должно быть важно, какой интерфейс / тип разрешен / активирован в ContentRegion — ему просто нужен объект для активации). Я использую MEF, поэтому этого можно достичь с помощью следующего блока кода:

 [Export("Calendar")]
public class CalendarView : UserControl, ICalendarView { }
 

Итак, могут ли представления знать друг о друге? Да! Например, у my EmailUserControl есть панель поиска / список электронных писем, а также панель предварительного просмотра. Эти 2 элемента управления могут быть EmailListUserControl , которые состоят из панели поиска и элемента управления ItemsControl, и EmailContentUserControl , который является просто панелью предварительного просмотра для выбранного электронного письма. Должны ли они быть отдельными элементами управления? Нет, но если они есть, мы можем повторно EmailContentUserControl использовать их, когда открываем электронное письмо в отдельном окне. Итак, это пример, в котором объект EmailUserControl связан с 2 разными представлениями (the EmailListUserControl и the EmailContentUserControl ).


Почему это лучше / отличается от других подходов: оно отделяет представления друг от друга (и защищает от моделей представлений, которым нужно знать о представлениях).

Комментарии:

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

2. EventAggregator не нужен, ContentController может быть IContentControllerService, который разрешается как сервис и внедряется в ваши модели представления (так же, как EventAggregator разрешается как IEventAggregator и вводится). Я просто подумал, поскольку это связано с PRISM, почему бы не использовать в полную силу все инструменты prism и не отделить view-model от ContentController. Чем меньше связей, тем проще модульное тестирование. На самом деле только «сервисы», которые вводятся, — это вещи, которые «объединяют» код воедино.

3. Суть в конечном итоге сводится к следующему: «Отделить представления друг от друга (и защитить от моделей представлений, которым нужно знать о представлениях)»… Я думаю, вы могли бы сказать, что смысл в том, чтобы сделать больше кода развязанным, если его не нужно связывать.

4. Я хочу сказать, что вы можете вводить представления в модели представлений без использования регионов и привязывать соответствующее свойство к ContentPlaceHolder элементу управления. В OP сказано «по сравнению с другими альтернативами», и это альтернатива, которую я успешно использовал в приложении. Но у меня не было агрегатора событий, что делает это решение более привлекательным. С его помощью, я думаю, вы можете отделить логику выбора вида от родительского представления. Я думаю, это означает, что родительский вид не должен знать о логике выбора, модель представления тоже не знает, и логика выбора не должна знать, какой родительский вид .

5. @MerlynMorgan-Graham: Это не очень хорошая идея, потому что она нарушает MVVM. Внедрение представления в ViewModel означает, что ваша ViewModel должна знать представление. ViewModel не должен знать, что такое a ContentControl , потому что это может быть привязка к консольному приложению, которое не использует a ContentControl . Это та же причина, по которой я никогда не использую FileDialog . Но, да, если вы хотите взломать MVVM, я думаю, самое близкое, что нужно сделать, это внедрить представление в виртуальную машину как тип ContentControl .

Ответ №3:

Сценарии, в которых этот регион позволяет

Я просмотрел эту статью, чтобы получить ответ: http://www.developmentalmadness.com/archive/2009/10/14/mvvm-and-prism-101-ndash-part-3-regions.aspx

Насколько я могу судить, функция Region предназначена для включения регистрации / внедрения представления в коде, который не является ни вашим представлением, ни вашей моделью представления.

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

Если вы использовали a ItemsControl и привязали его к произвольным данным в модели представления, вам нужно будет указать, какие представления создавать в DataTemplate в главном представлении.

С помощью области вы можете зарегистрировать представление области в контейнере внедрения зависимостей. Ни представление, ни соответствующие модели представления не должны ничего знать о конкретном представлении, которое будет использоваться во время выполнения. Он будет введен контейнером.

Это позволяет вам полностью отделить основное представление от знания чего-либо о его дочерних представлениях, не заставляя вашу модель представления что-либо знать об этих дочерних представлениях.

Конкретные варианты использования

Насколько это полезно и какие конкретные сценарии это позволит, я не уверен. Я использовал a ContentPresenter с архитектурой плагинов, но я не уверен, что это хорошо подходит для этой модели. С помощью модели плагина вы хотите, чтобы представление и модель представления были связаны друг с другом, поэтому такой подход вам ничего не даст.

Я полагаю, было бы лучше всего, если бы вы обнаружили, что у вас большие несвязанные представления, и хотите разделить их. Это может быть полезно для изоляции ваших интеграционных тестов друг от друга, путем одновременного ввода только части представления и модели представления.

Я хватаюсь за соломинку для убедительных реальных вариантов использования 🙂 Почти во всех случаях вы могли бы просто использовать a UserControl вместо этого и отказаться от всей динамической регистрации, без каких-либо реальных недостатков.

Комментарии:

1. Примечание: я никогда не использовал Prism. Я не знаю, правильно ли они используют контейнер для внедрения зависимостей (контейнер видит только ваш код точки входа), или они действительно поощряют или требуют, чтобы вы использовали шаблон ServiceLocator . Похоже, что эти статьи делают это неправильно 🙂 При правильном контейнере DI вы никогда не захотите вызывать Resolve , кроме как в своей Main функции (или эквиваленте — метод запуска приложения в WPF). Некоторые библиотеки DI не разработали свой контейнер с учетом этого использования, поэтому авторам этих контейнеров будет казаться совершенно естественным не согласиться со мной. Не уверен, что Prism такой или нет.