MVVM имеет много преимуществ, но по моему опыту соединение моделей представлений и представлений является одной из самых больших сложностей.
Существует два основных способа сделать это:
1:
Подключить модели видов к видам.
В этом сценарии XAML для MainWindow
содержит дочерние элементы управления.В вашем случае некоторые из этих представлений, вероятно, будут скрыты (поскольку вы одновременно показываете только один экран).
Модели представлений подключаются к представлениям, обычно одним из двух способов:
В приведенном ниже коде после вызова InitializeComponents()
или в обработчике события this.Loaded
укажите this.DataContext = container.Resolve<MyViewModelType>();
. Обратите внимание, что в этом случае контейнер должен быть доступен глобально.Это типично для приложений, которые используют Unity.Вы спросили, как дети будут разрешать такие интерфейсы, как IStatementWriter
.Если контейнер является глобальным, дочерние модели представлений могут просто вызвать container.Resolve<IStatementWriter>();
Другой способ связать модели представлений с представлениями - создать экземпляр модели представления в XAML, например:
<UserControl ...>
<UserControl.DataContext>
<local:MyViewModelType/>
</UserControl.DataContext>
...
</UserControl>
Этот метод не совместим с Unity.Существует несколько сред MVVM, которые позволяют разрешать типы в XAML (я полагаю, что Caliburn делает).Эти платформы реализуют это с помощью расширений разметки .
2:
Подключите представление к модели представления.
Обычно это мой предпочтительный метод, хотя он усложняет дерево XAML.Этот метод работает очень хорошо, когда вам нужно выполнить навигацию в модели основного вида.
Создайте объекты модели дочернего вида в модели основного вида.
public class MainViewModel
{
public MyViewModelType Model1 { get; private set; }
public ViewModelType2 Model2 { get; private set; }
public ViewModelType3 Model3 { get; private set; }
public MainViewModel()
{
// This allows us to use Unity to resolve the view models!
// We can use a global container or pass it into the constructor of the main view model
// The dependencies for the child view models could then be resolved in their
// constructors if you don't want to make the container global.
Model1 = container.Resolve<MyViewModelType>();
Model2 = container.Resolve<ViewModelType2>();
Model3 = container.Resolve<ViewModelType3>();
CurrentViewModel = Model1;
}
// You will need to fire property changed notifications here!
public object CurrentViewModel { get; set; }
}
В главном представлении создайте один или несколько элементов управления содержимым и задайте для содержимого (-ей) модели представления, которые вы хотите отобразить.
<Window ...>
...
<ContentControl Content="{Binding CurrentViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:MyViewModelType}">
<local:MyViewType/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelType2}">
<local:ViewType2/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelType3}">
<local:ViewType3/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
...
</Window>
Обратите внимание, что мы связываемребенок просматривает модели с помощью шаблонов данных на ContentControl
.Эти шаблоны данных могли быть определены на уровне Window
или даже на уровне Application
, но мне нравится помещать их в контекст, чтобы было легче увидеть, как представления привязываются к моделям представления.Если бы у нас был только один тип модели представления для каждого ContentControl
, мы могли бы использовать свойство ContentTemplate
вместо использования ресурсов.
РЕДАКТИРОВАТЬ: В этом методе модели представлений могут быть разрешены с помощью внедрения зависимостей, но представления разрешаются с помощью механизма разрешения ресурсов WPF.Вот как это работает:
Когда для содержимого ContentPresenter
(базового компонента в ContentControl
) задан объект, который НЕ является визуальным (не производным от класса Visual
), WPF ищет шаблон данных для отображения объекта.Сначала он использует любые явные шаблоны данных, установленные на элементе управления хоста (например, свойство ContentTemplate
в ContentControl
).Затем он ищет логическое дерево, исследуя ресурсы каждого элемента в дереве для DataTemplate
с ключом ресурса {x:Type local:OBJECT_TYPE}
, где OBJECT_TYPE - тип данных контента.Обратите внимание, что в этом случае он находит шаблоны данных, которые мы определили локально.Когда стиль, шаблон элемента управления или шаблон данных определены с целевым типом, но не с именованным ключом, тип становится ключом.Window
и Application
находятся в логическом дереве, поэтому ресурсы / шаблоны, определенные здесь, также будут найдены и разрешены, если они не будут расположены в ресурсах элемента управления хоста.
Один последний комментарий.Если шаблон данных не найден, WPF вызывает ToString()
для объекта содержимого и использует результат в качестве визуального содержимого.Если ToString()
не переопределено каким-либо значимым образом, результатом будет TextBlock
, содержащий тип содержимого.<- </p>
Когда вы обновляете свойство CurrentViewModel
на MainViewModel
, содержимое и представление в главном представлении будут автоматически изменяться, пока вы запускаете уведомление об изменении свойства в модели основного представления.
Дайте мне знать, если я что-то пропустил или вам нужна дополнительная информация.