MVVM 架构中,ViewModel 构造函数时还是之后加载数据更好?

30

我的问题是,是否最好在ViewModel构建期间加载数据,还是通过某些Loaded事件处理之后加载数据呢?

我猜答案是通过Loaded事件处理之后加载数据,但是我想知道如何在ViewModel和View之间最清晰地协调这个过程。

以下是关于我的情况和我正在尝试解决的特定问题的更多细节:

我正在使用MVVM Light框架以及Unity进行依赖注入。我有一些嵌套的Views,每个View都绑定到相应的ViewModel。ViewModels通过ViewModelLocator的概念绑定到每个View的根控件DataContext,该概念由Laurent Bugnion放入MVVM Light中。这允许通过静态资源查找ViewModels,并通过Dependency Injection框架(在这种情况下为Unity)控制ViewModels的生命周期。它还允许Expression Blend查看有关ViewModels以及如何绑定它们的所有内容。

所以,我有一个父View,其中有一个ComboBox与其ViewModel中的ObservableCollection数据绑定。ComboBox的SelectedItem也绑定到ViewModel的一个属性(双向)。当ComboBox的选择改变时,这将触发其他视图和子视图的更新。目前,我是通过MVVM Light中的消息系统来实现这一点的。当你在ComboBox中选择不同的项目时,它都可以很好地工作,一切都符合预期。

然而,ViewModel是在构建期间通过一系列初始化方法调用获取其数据的。如果我想控制ComboBox的初始SelectedItem,这似乎只是一个问题。使用MVVM Light的消息系统,我目前已经设置了ViewModel的SelectedItem属性的setter广播更新,并且其他相关的ViewModel在它们的构造函数中注册该消息。目前似乎我正在尝试在ViewModel构建时间通过ViewModel来设置SelectedItem,这导致子ViewModel还未被构造和注册。

如何在ViewModel中协调数据加载和初始化SelectedItem的最佳方式?我希望尽可能少地将代码放入View的code-behind。我认为我只需要一种方式让ViewModel知道何时已加载内容,以便它可以继续加载数据并完成设置阶段。

提前感谢您的回复。


1
你不能让你的Loaded事件调用视图模型上的方法吗? - Klinger
1
是的,我想我可以。我可能想太多了。我犹豫的原因是到目前为止,我已经能够在XAML中声明性地绑定所有内容。我设置了DataContext,然后在一个地方设置了成员绑定。是否有一种干净的方法在XAML中继续使用此方法,并将控件的Loaded事件绑定到ViewModel方法?当然,我认为ViewModel也不应该具有UI特定的事件处理参数。 - mkmurray
5个回答

28

对于事件,您应该使用MVVM Light Toolkit中的EventToCommand。使用此工具,您可以将任何UI元素的任何事件绑定到RelayCommand。请查看他在EventToCommand上的文章。

下载示例并查看。它非常棒。然后您将不需要任何CodeBehind。以下是一个示例:

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

}

啊..我正在寻找一个MVVM Toolkit的解决方案,即简单的mvvm模式。你能否提供一个建议?谢谢 - Farrukh Waheed

6
以下解决方案与已提供和接受的解决方案类似,但它不使用视图模型中的命令来加载数据,而是使用“普通方法”。 我认为命令更适合用户操作(在运行时可以使用和不可用),这就是为什么要使用常规方法调用,并通过在视图中设置交互触发器来实现。
我建议如下: 创建一个视图模型类。 通过在 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>   

当视图加载时,它将在ViewModel中调用LoadData方法。这是您加载数据的地方。

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

好的,没问题. :-)

您可以通过使用行为(bahavior)来将ViewModel绑定到一个方法。

这里有一个链接可以帮助您完成这个过程。 http://expressionblend.codeplex.com/


1

我决定将XAML声明性地绑定到View的代码后台上的Loaded事件处理程序,然后通过View的根元素UserControl DataContext调用ViewModel对象上的方法。

这是一个相当简单、直接和清晰的解决方案。我想我希望能够以与XAML中的ICommands相同的声明方式将Loaded事件绑定到ViewModel对象。

我可能已经给了Klinger官方答案的信用,但他只是在我的问题下发表了评论,而不是回答。所以我至少给了他一个赞。


0
当我处理父窗口和子窗口之间的消息时,我遇到了同样的问题。只需更改ViewModelLocator类中创建视图模型的顺序即可。确保在发送消息的视图模型之前创建所有依赖于消息的视图模型。
例如,在您的ViewModelLocator类的构造函数中:
public ViewModelLocator()
{
    if (s_messageReceiverVm == null)
    {
        s_messageReceiverVm = new MessageReceiverVM();
    }

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

1
您可以通过ViewModelLocator这种方式初始化VM:SimpleIoc.Default.Register<messageReceiverVm>(true); - D.Rosado
我不明白你的评论与我的答案有什么关联。你能解释一下吗? - bugged87
1
我刚刚提出了一种替代方式(使用Ioc),在ViewModelLocator构造函数中创建ViewModels。 - D.Rosado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接