MVVM загружает данные во время или после конструкции ViewModel?
Мой общий вопрос, как указано в заголовке, лучше всего загружать данные во время конструкции ViewModel или позже через некоторую обработку событий Loaded?
Я предполагаю, что ответ после построения через некоторую обработку событий Loaded, но мне интересно, как это наиболее четко согласовано между ViewModel и View?
Здесь более подробно о моей ситуации и конкретной проблеме, которую я пытаюсь решить:
Я использую структуру MVVM Light, а также Unity для DI. У меня есть несколько вложенных представлений, каждая связана с соответствующей ViewModel. ViewModels привязаны к каждому представлению корневого управления DataContext с помощью идеи ViewModelLocator, которую Лоран Бугнион внес в MVVM Light. Это позволяет находить ViewModels через статический ресурс и управлять временем жизни ViewModels через инфраструктуру Injection Dependency, в этом случае Unity. Он также позволяет Expression Blend видеть все в отношении ViewModels и как их связывать.
Так или иначе, у меня есть родительский вид, у которого есть привязка к ComboBox к ObservableCollection в его ViewModel. ComboBox SelectedItem также привязан (в двух направлениях) к свойству ViewModel. Когда выбор ComboBox изменяется, это значит инициировать обновления в других представлениях и представлениях. В настоящее время я выполняю это через систему обмена сообщениями, которая находится в MVVM Light. Все это отлично работает и, как ожидается, когда вы выбираете разные элементы в ComboBox.
Тем не менее, ViewModel получает свои данные во время построения через серию вызовов вызова инициализации. Кажется, это проблема только в том случае, если я хочу контролировать, что такое исходный SelectedItem из ComboBox. Используя MVVM Light messaging system, я в настоящее время настроил его, когда установщик свойства ViewModel SelectedItem является тем, кто передает обновление, а другие заинтересованные регистры ViewModels для сообщения в своих конструкторах. Похоже, что в настоящее время я пытаюсь установить SelectedItem через ViewModel во время построения, что не позволяет создавать и регистрировать суб-ViewModels.
Каким будет самый чистый способ координации загрузки данных и начальной настройки SelectedItem в ViewModel? Я действительно хочу придерживаться так мало, как разумно. Я думаю, мне просто нужно, чтобы ViewModel узнал, когда материал загружен, и что он может продолжить загрузку данных и завершить настройку.
Заранее благодарим за ваши ответы.
Ответы
Ответ 1
Для событий вы должны использовать EventToCommand в MVVM Light Toolkit. Используя это, вы можете связать любое событие любого элемента ui с командой relaycommand. Просмотрите его статью о EventToCommand в
http://blog.galasoft.ch/archive/2009/11/05/mvvm-light-toolkit-v3-alpha-2-eventtocommand-behavior.aspx
Загрузите образец и посмотрите. Здорово. Тогда вам не понадобится код. Пример следующий:
<Page x:Class="cubic.cats.Wpf.Views.SplashScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SplashScreenPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Label Content="This is test page" />
</Grid>
</Page>
и режим просмотра может быть таким:
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand(() =>
{
string a = "put a break point here to see that it gets called after the view as been loaded";
});
}
}
если вы хотите, чтобы модель представления имела EventArgs, вы можете просто установить PassEventArgsToCommand в true:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
и модель представления будет выглядеть как
public class SplashScreenViewModel : ViewModelBase
{
public RelayCommand<MouseEventArgs> LoadedCommand
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the SplashScreenViewModel class.
/// </summary>
public SplashScreenViewModel()
{
LoadedCommand = new RelayCommand<MouseEventArgs>(e =>
{
var a = e.WhateverParameters....;
});
}
}
Ответ 2
Хорошо, тогда.: -)
Вы можете привязываться к методу в ViewModel с помощью поведения.
Вот ссылка, которая поможет вам в этом.
http://expressionblend.codeplex.com/
Ответ 3
Я решил, что XAML декларативно привязан к обработчику событий Loaded в коде кода View, который, в свою очередь, просто называется методом объекта ViewModel, через элемент root root UserControl DataContext.
Это было довольно простое, прямолинейное и чистое решение. Наверное, я надеялся на способ привязки события Loaded к объекту ViewModel тем же декларативным способом, который вы можете использовать с ICommands в XAML.
Возможно, я дал Клингеру официальный ответ, но он отправил комментарий к моему вопросу, а не ответ. Поэтому я, по крайней мере, дал ему один комментарий к его комментарию.
Ответ 4
Следующее решение похоже на уже предоставленное и принятое, но оно не использует команду в модели представления для загрузки данных, а "обычный метод".
Я думаю, что команды более подходят для пользовательских действий (команды могут быть доступны и недоступны во время выполнения), поэтому использовать обычный вызов метода, но также путем установки триггера взаимодействия в представлении.
Я предлагаю следующее:
Создайте класс модели представления.
Создайте экземпляр класса модели представления в пределах xaml представления, создав его внутри свойства DataContext
.
Реализовать метод загрузки данных в вашей модели представления, например. LoadData
.
Настройте представление, чтобы этот метод вызывался при загрузке представления.
Это делается с помощью триггера взаимодействия в вашем представлении, связанного с методом в модели представления (необходимы ссылки на "Microsoft.Expression.Interactions" и "System.Windows.Interactivity" ):
Вид (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
>
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Это вызовет метод LoadData
в ViewModel во время выполнения при загрузке представления. Здесь вы загружаете свои данные.
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT do complex stuff here
}
public void LoadData()
{
// Make a call to the repository class here
// to set properties of your view model
}
Если метод в репозитории является асинхронным методом, вы можете также сделать асинхронный метод LoadData
, но это не нужно в каждом случае.
Кстати, вообще-то я не загружал данные в конструктор модели представления.
В приведенном выше примере конструктор (параметр less) модели представления вызывается, когда дизайнер показывает ваше представление. Выполнение сложных вещей здесь может привести к ошибкам в дизайнере при показе вашего представления (по той же причине я не буду создавать сложные вещи в конструкторе представлений).
В некоторых сценариях код конструктора представлений может даже вызвать проблемы во время выполнения, когда выполняются конструкторы моделей представлений, задает свойства модели представления, привязанные к элементам в представлении, в то время как объект вида не полностью завершен.
Ответ 5
У меня была такая же проблема при работе с сообщениями между родительским окном и дочерним окном. Просто измените порядок, в котором ваши модели просмотра создаются в вашем классе ViewModelLocator. Убедитесь, что все модели представлений, зависящие от сообщения, создаются перед моделью представления, которая отправляет сообщение.
Например, в конструкторе класса ViewModelLocator:
public ViewModelLocator()
{
if (s_messageReceiverVm == null)
{
s_messageReceiverVm = new MessageReceiverVM();
}
if (s_messageSenderVm == null)
{
s_messageSenderVm = new MessageSenderVM();
}
}