29

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

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

以下是有关我的情况和我要解决的特定问题的更多详细信息:

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

所以无论如何,我有一个父视图,它有一个 ComboBox 数据绑定到其 ViewModel 中的 ObservableCollection。ComboBox 的 SelectedItem 也绑定(双向)到 ViewModel 上的一个属性。当 ComboBox 的选择发生变化时,这是为了触发其他视图和子视图中的更新。目前,我正在通过 MVVM Light 中的消息系统来完成此操作。当您在 ComboBox 中选择不同的项目时,这一切都很好,并且符合预期。

但是,ViewModel 在构建期间通过一系列初始化方法调用获取其数据。如果我想控制 ComboBox 的初始 SelectedItem 是什么,这似乎只是一个问题。使用 MVVM Light 的消息传递系统,我目前已将其设置为 ViewModel 的 SelectedItem 属性的设置器是广播更新的设置器,而其他感兴趣的 ViewModels 在其构造函数中注册消息。看来我目前正在尝试在构建时通过 ViewModel 设置 SelectedItem,这还不允许构建和注册子 ViewModel。

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

提前感谢您的回复。

4

5 回答 5

27

对于事件,您应该使用 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>

并且查看模式可能是这样的

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

}
于 2010-08-03T11:36:55.603 回答
5

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

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

实现一种方法以在视图模型中加载数据,例如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方法异步,但在每种情况下都不需要这样做。

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

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

于 2015-08-03T15:33:06.797 回答
2

那好吧。:-)

您可以使用行为绑定到 ViewModel 中的方法。

这是一个可以帮助您的链接。 http://expressionblend.codeplex.com/

于 2010-03-29T15:55:08.277 回答
1

我决定让 XAML 以声明方式绑定到 View 的代码隐藏上的 Loaded 事件处理程序,而后者又通过 View 的根元素 UserControl DataContext 调用 ViewModel 对象上的方法。

这是一个相当简单、直接且干净的解决方案。我想我希望有一种方法可以将 Loaded 事件以与 XAML 中的 ICommands 相同的声明方式绑定到 ViewModel 对象。

我可能给了克林格官方答案,但他对我的问题发表了评论,而不是答案。所以我至少对他的评论给予了肯定。

于 2010-03-28T20:54:41.963 回答
0

在处理父窗口和子窗口之间的消息时,我遇到了同样的问题。只需更改在 ViewModelLocator 类中创建视图模型的顺序。确保在发送消息的视图模型之前创建所有依赖于消息的视图模型。

例如,在您的 ViewModelLocator 类的构造函数中:

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

    if (s_messageSenderVm == null)
    {
        s_messageSenderVm = new MessageSenderVM();
    }
}
于 2012-09-19T22:30:38.107 回答