MVVM - какой идеальный способ для пользовательских контроллеров общаться друг с другом
У меня есть пользовательский элемент управления, который содержит несколько других пользовательских элементов управления. Я использую MVVM. Каждый пользовательский элемент управления имеет соответствующую виртуальную машину. Как эти пользовательские элементы управления отправляют информацию друг другу? Я хочу избежать написания какого-либо кода в коде xaml. В частности, меня интересует, как элементы управления (внутри основного пользовательского элемента управления) будут общаться друг с другом и как они будут взаимодействовать с пользовательским элементом управления контейнера.
РЕДАКТИРОВАТЬ: Я знаю, что использование событий-делегатов поможет мне решить эту проблему. Но я хочу избежать написания любого кода в коде позади xaml.
9 ответов
Как правило, лучше всего попытаться уменьшить объем связи между частями, поскольку каждый раз, когда два пользовательских элемента управления "общаются" друг с другом, вы вводите зависимость между ними.
Это, как говорится, есть несколько вещей, чтобы рассмотреть:
- UserControls всегда может "общаться" со своим управляющим элементом посредством предоставления свойств и использования DataBinding. Это очень приятно, так как сохраняет стиль MVVM во всех аспектах.
- Содержащий элемент управления может использовать свойства для "связывания" двух свойств в двух пользовательских элементах управления, опять же, сохраняя чистые границы
Если вам нужно более четкое общение, есть два основных подхода.
- Реализуйте службу, общую для обоих элементов, и используйте внедрение зависимостей, чтобы обеспечить реализацию во время выполнения. Это позволяет элементам управления взаимодействовать со службой, которая, в свою очередь, может синхронизировать элементы управления, но также сводит зависимость к минимуму.
- Используйте некоторую форму обмена сообщениями для передачи сообщений между элементами управления. Многие MVVM-фреймворки используют этот подход, поскольку он разъединяет отправку сообщения от получения сообщения, опять же, сводя зависимости к минимуму.
Ваша концептуальная проблема здесь:
Каждый пользовательский элемент управления имеет соответствующую виртуальную машину.
Наличие отдельной ViewModel для каждого представления в значительной степени опровергает концепцию ViewModel. ViewModels не должны быть один-на-один с представлениями, в противном случае они не что иное, как прославленный код позади.
ViewModel отражает концепцию "текущего состояния пользовательского интерфейса" - например, на какой странице вы находитесь и редактируете или нет - в отличие от "текущих значений данных".
Чтобы действительно воспользоваться преимуществами MV-VM, определите количество используемых классов ViewModel на основе отдельных элементов, которые нуждаются в состоянии. Например, если у вас есть список элементов, каждый из которых может отображаться в 3 состояниях, вам потребуется одна виртуальная машина на элемент. И наоборот, если у вас есть три представления, каждое из которых отображает данные тремя различными способами в зависимости от общего параметра, общий параметр должен быть записан в одной виртуальной машине.
После того, как вы создали свои ViewModels, чтобы отразить требования поставленной задачи, вы, как правило, обнаруживает, что нет необходимости или желания сообщать состояние между представлениями. Если есть такая необходимость, лучше всего переоценить дизайн ViewModel, чтобы увидеть, может ли совместно используемая ViewModel извлечь выгоду из небольшого количества дополнительной информации о состоянии.
Будут времена, когда сложность приложения диктует использование нескольких ViewModel для одного и того же объекта модели. В этом случае ViewModels может хранить ссылки на общий объект состояния.
Для этого существует множество различных механизмов, но сначала вы должны выяснить, к какому уровню вашей архитектуры относится это общение.
Одна из целей инфраструктуры MVVM заключается в том, что для одной и той же модели представления могут быть созданы разные представления. Будут ли эти пользовательские элементы управления взаимодействовать друг с другом только в представлении, которое вы в настоящее время реализуете, или им придется взаимодействовать друг с другом в других возможных представлениях? В последнем случае вы хотите реализовать его ниже уровня представления, либо в модели представления, либо в самой модели.
Пример первого случая может быть, если ваше приложение работает на очень маленькой поверхности дисплея. Возможно, ваши пользовательские элементы управления должны конкурировать за визуальное пространство. Если пользователь щелкает один элемент управления пользователя, чтобы максимизировать, другие должны свернуть. Это не имеет ничего общего с моделью представления, это просто адаптация к технологии.
Или, может быть, у вас разные модели представления с разными пользовательскими элементами управления, где все может происходить без изменения модели. Примером этого может быть навигация. У вас есть список чего-либо и панель сведений с полями и командными кнопками, которые связаны с выбранным элементом в списке. Возможно, вы захотите провести модульное тестирование логики того, какие кнопки включены для каких элементов. Модель не заботится о том, на какой элемент вы смотрите, только когда нажимаются команды кнопок или изменяются поля.
Необходимость такого общения может быть даже в самой модели. Возможно, у вас есть денормализованные данные, которые обновляются, потому что изменяются другие данные. Тогда различные действующие модели вида должны измениться из-за ряби изменений в модели.
Итак, подведем итоги: "Это зависит...."
На мой взгляд, лучший способ сделать это - через Commanding (Routed Commands / RelayCommand и т. Д.).
Я хочу избежать написания какого-либо кода в коде xaml.
Хотя это похвальная цель, вы должны применить к ней немного практичности, она не должна применяться на 100% как правило типа "не бойся".
Я думаю, что лучшим решением будет использование шаблона Publisher/Subscriber. Каждый элемент управления регистрирует некоторые события и присоединяет удаленные файлы к событиям, предоставляемым другими элементами управления.
Для того, чтобы выставлять события и прикрепляться к ним, вам нужно будет использовать какую-то услугу Mediator/EventBroker. Я нашел хороший пример здесь
Вы можете поделиться некоторыми объектами View Model между элементами управления, а также с командами...
Например, у вас есть основной элемент управления, который содержит два других элемента управления. И у вас есть некоторые функции фильтрации в главном элементе управления, но вы хотите, чтобы пользователь мог установить некоторую часть фильтра в первом подэлементе управления (например, "Полный фильтр") и некоторую часть фильтра в другом (например, "Быстрый фильтр"). "). Также вы хотите иметь возможность начать фильтрацию с любого из элементов управления. Тогда вы можете использовать такой код:
public class MainControlViewModel : ObservableObject
{
public FirstControlViewModel firstControlViewModel;
public SecondControlViewModel firstControlViewModel;
public ICommand FilterCommand;
public FilterSettings FilterSettings;
public MainControlViewModel()
{
//...
this.firstControlViewModel = new FirstControlViewModel(this.FilterSettings, this.FilterCommand);
this.secondControlViewModel = new SecondControlViewModel(this.FilterSettings, this.FilterCommand);
}
}
public class FirstControlViewModel : ObservableObject
{
//...
}
public class SecondControlViewModel : ObservableObject
{
//...
}
В основном элементе управления XAML вы будете связывать подконтрольные элементы DataContext с соответствующими моделями представления. Всякий раз, когда суб-элемент управления изменяет настройку фильтра или выполняет команду, другой суб-элемент управления будет уведомлен.
Как уже говорили другие, у вас есть несколько вариантов.
Предоставление DepedencyProperties вашим пользовательским элементам управления и привязка к этим свойствам в большинстве случаев обеспечивают чистое решение XAML, но могут вводить некоторые зависимости пользовательского интерфейса, чтобы привязки могли видеть друг друга.
Другой вариант - это разъединенный шаблон обмена сообщениями для отправки сообщений между ViewModels. Я хотел бы, чтобы ваши пользовательские элементы управления привязывались к свойствам своих собственных виртуальных машин, а затем к изменению свойств внутри этой виртуальной машины могли бы "публиковать" сообщение, уведомляющее других "подписчиков" о том, что что-то произошло, и они могут реагировать на это сообщение, как они хотят,
У меня есть пост в блоге на эту тему, если это поможет: http://www.bradcunningham.net/2009/11/decoupled-viewmodel-messaging-part-1.html
Вы можете связываться между элементами в пользовательском интерфейсе, используя привязку элементов, поэтому, если созданный пользовательский элемент управления предоставляет свойство, другие пользовательские элементы управления могут связываться с ним. Вы можете настроить привязку, использовать свойства зависимости вместо базовых свойств / реализовать INotifyPropertyChanged, но это теоретически возможно, но требует некоторой предусмотрительности для включения связи таким образом.
Возможно, вам будет гораздо проще использовать комбинацию событий, кода и свойств, чем использовать чисто декларативный способ, но теоретически это возможно.
Если вы используете строгий MVVM, то пользовательский элемент управления является представлением и должен только "говорить", или, скорее, связываться с его моделью представления. Поскольку ваши ViewModels, скорее всего, уже реализуют INotifyPropertyChanged, при условии, что они имеют ссылку друг на друга, они могут использовать события PropertyChanged, чтобы получать уведомления при изменении свойств, или они могут вызывать методы (лучше, если это через интерфейс) для взаимодействия с каждым Другой.