MVVM загружает данные во время или после построения ViewModel? - PullRequest
25 голосов
/ 28 марта 2010

Мой общий вопрос заключается в том, что в заголовке говорится, лучше ли загружать данные во время построения ViewModel или после какой-либо обработки Loaded-событий?

Я предполагаю, что ответ получен после построения с помощью некоторой обработки Loaded-событий, но мне интересно, как это наиболее четко скоординировано между ViewModel и View?

Вот более подробная информация о моей ситуации и конкретной проблеме, которую я пытаюсь решить:

Я использую инфраструктуру MVVM Light, а также Unity для DI. У меня есть несколько вложенных видов, каждый из которых связан с соответствующей ViewModel. ViewModels связаны с каждым корневым элементом управления DataContext через идею ViewModelLocator, которую Лоран Буньон добавил в MVVM Light. Это позволяет находить ViewModels через статический ресурс и управлять временем жизни ViewModels через платформу Dependency Injection, в данном случае Unity. Это также позволяет Expression Blend видеть все, что касается ViewModels и как их связывать.

Так или иначе, у меня есть родительский View, имеющий привязку ComboBox к ObservableCollection в его ViewModel. SelectedItem ComboBox также привязан (двусторонний) к свойству в ViewModel. Когда выбор ComboBox изменяется, это вызывает обновления в других представлениях и подпредставлениях. В настоящее время я выполняю это через систему сообщений, которая находится в MVVM Light. Все это прекрасно работает и, как и следовало ожидать, когда вы выбираете различные элементы в ComboBox.

Тем не менее, ViewModel получает свои данные во время сборки через серию вызовов методов инициализации. Это кажется проблемой только в том случае, если я хочу контролировать исходный SelectedItem ComboBox. Используя систему обмена сообщениями MVVM Light, я в настоящее время настроил ее так, что установщик свойства SelectedItem ViewModel - это тот, который передает обновление, а другие заинтересованные ViewModels регистрируют сообщение в своих конструкторах. Похоже, что в настоящее время я пытаюсь установить SelectedItem через ViewModel во время построения, что еще не позволило создавать и регистрировать под-модели моделей.

Каким будет самый чистый способ согласовать загрузку данных и начальную настройку SelectedItem внутри ViewModel? Я действительно хочу придерживаться минимального количества кода в представлении, насколько это разумно. Я думаю, что мне просто нужен способ, чтобы ViewModel мог узнать, когда что-то загрузилось, и затем он может продолжить загрузку данных и завершить этап установки.

Заранее спасибо за ваши ответы.

Ответы [ 5 ]

24 голосов
/ 03 августа 2010

Для событий вы должны использовать EventToCommand в MVVM Light Toolkit. Используя это, вы можете привязать любое событие любого элемента пользовательского интерфейса к 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....;
        });
    }

}
3 голосов
/ 03 августа 2015

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

Я предлагаю это: Создайте класс модели представления. Создайте экземпляр класса модели представления в 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 асинхронным, но это не требуется в каждом случае.

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

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

2 голосов
/ 29 марта 2010

Хорошо, тогда. : -)

Вы можете привязать метод в ViewModel с помощью поведения.

Вот ссылка, которая поможет вам в этом. http://expressionblend.codeplex.com/

1 голос
/ 29 марта 2010

Я решил просто связать XAML, декларативно связанный с обработчиком события Loaded в программном коде View, который в свою очередь просто вызвал метод объекта ViewModel через корневой элемент View UserControl DataContext.

Это было довольно простое, прямое и чистое решение. Наверное, я надеялся найти способ связать событие Loaded с объектом ViewModel таким же декларативным способом, каким вы можете использовать ICommands в XAML.

Возможно, я дал Клинджеру официальный ответ, но он оставил комментарий к моему вопросу, а не ответ. Поэтому я, по крайней мере, дал ему один комментарий к нему.

0 голосов
/ 20 сентября 2012

У меня была такая же проблема при работе с сообщениями между родительским окном и дочерним окном. Просто измените порядок создания моделей представлений в вашем классе ViewModelLocator. Убедитесь, что все модели представлений, которые зависят от сообщения, созданы до модели представления, которая отправляет сообщение.

Например, в конструкторе вашего класса ViewModelLocator:

public ViewModelLocator()
{
    if (s_messageReceiverVm == null)
    {
        s_messageReceiverVm = new MessageReceiverVM();
    }

    if (s_messageSenderVm == null)
    {
        s_messageSenderVm = new MessageSenderVM();
    }
}
...