在ViewModel构建期间或之后MVVM加载数据?

mkm*_*ray 26 data-binding constructor onload viewmodel mvvm-light

我的通用问题是标题所述,是否最好在ViewModel构造期间或之后通过某些Loaded事件处理加载数据?

我猜测答案是在构建之后通过一些Loaded事件处理,但我想知道ViewModel和View之间如何最清晰地协调?

这里有关于我的情况以及我想要解决的特定问题的更多细节:

我正在使用MVVM Light框架以及Unity for DI.我有一些嵌套的视图,每个视图都绑定到相应的ViewModel.ViewModel通过Laurent Bugnion放入MVVM Light的ViewModelLocator理念绑定到每个View的根控件DataContext.这允许通过静态资源查找ViewModels并通过依赖注入框架控制ViewModels的生命周期,在本例中为Unity.它还允许Expression Blend查看与ViewModel相关的所有内容以及如何绑定它们.

所以无论如何,我有一个父View,它有一个ComboBox数据绑定到其ViewModel中的ObservableCollection.ComboBox的SelectedItem也绑定(双向)到ViewModel上的属性.当ComboBox的选择发生变化时,这将触发其他视图和子视图中的更新.目前我正在通过MVVM Light中的Messaging系统实现这一目标.当您在ComboBox中选择不同的项目时,这一切都非常有效.

但是,ViewModel通过一系列初始化方法调用在构造期间获取其数据.如果我想控制ComboBox的初始SelectedItem是什么,这似乎只是一个问题.使用MVVM Light的消息传递系统,我目前已经设置了ViewModel的SelectedItem属性的setter是广播更新的设置者,另一个感兴趣的ViewModels注册用于它们的构造函数中的消息.看来我正在尝试在构造时通过ViewModel设置SelectedItem,这样就不允许构建子ViewModel并进行注册.

在ViewModel中协调数据加载和SelectedItem初始设置的最简洁方法是什么?我真的很想坚持在View的代码隐藏中尽可能少地使用合理的代码.我想我只需要一种方法让ViewModel知道什么时候有东西已经加载,然后它可以继续加载数据并完成设置阶段.

在此先感谢您的回复.

nab*_*rid 24

对于事件,您应该使用MVVM Light Toolkit中的EventToCommand.使用此方法,您可以将任何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>
Run Code Online (Sandbox Code Playgroud)

并且视图模式可以是这样的

 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";
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

如果您希望视图模型具有EventArgs,您可以简单地将PassEventArgsToCommand设置为true:

<i:Interaction.Triggers>
            <i:EventTrigger EventName="Loaded">
                <cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding LoadedCommand}" />
  </i:EventTrigger>        
</i:Interaction.Triggers>
Run Code Online (Sandbox Code Playgroud)

而视图模型就像

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....;
        });
    }

}
Run Code Online (Sandbox Code Playgroud)


Mar*_*tin 5

下面的解决方案与已经提供并接受的解决方案类似,但它没有使用视图模型中的命令来加载数据,而是一种“正常方法”。我认为命令更适合用户操作(命令在运行时可用但不可用),这就是为什么使用常规方法调用,但也通过在视图中设置交互触发器。

我建议这样做:创建一个视图模型类。通过在DataContext属性中创建视图的 xaml 中的视图模型类来实例化它。

实现一个方法来在你的视图模型中加载数据,例如LoadData。设置视图,以便在视图加载时调用此方法。这是由视图中的交互触发器完成的,该触发器链接到视图模型中的方法(需要引用“Microsoft.Expression.Interactions”和“System.Windows.Interactivity”):

查看(xml):

<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>   
Run Code Online (Sandbox Code Playgroud)

这将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
    }
Run Code Online (Sandbox Code Playgroud)

如果存储库中的方法是异步方法,您也可以将LoadData方法设为异步,但并非在每种情况下都需要这样做。

顺便说一句,通常我不会在视图模型的构造函数中加载数据。在上面的示例中,当设计器显示您的视图时,会调用视图模型的(无参数)构造函数。在这里做复杂的事情会导致设计器在显示视图时出错(出于同样的原因,我不会在视图构造函数中做复杂的事情)。

在某些场景中,视图模型构造函数中的代码甚至会在运行时导致问题,当视图模型构造函数执行时,设置绑定到视图元素的视图模型的属性,而视图对象还没有完全创建完成。