如何在一个窗口中的VM之间创建MVVM父/子关系?

时间:2011-07-13 16:03:10

标签: mvvm parent-child

我正在研究一次性应用程序,但我喜欢用这些作为一个机会,通过不熟悉的东西来提高我的技能。所以我决定使用MVVM和WPF而不是坚持我的WinForms舒适区。我无法弄清楚如何让我的UI与他人交谈。嗯,这更像是我找不到一种方法来做这件事并不会让人感到不舒服。

应用程序的测试性能,用于确定大型集合数据中切片的最小值/最大值。 Here's what it looks like.

主窗口的ViewModel生成一个包含许多值的集合。我想为切片指定一个范围,然后执行几个版本的最小/最大算法来验证正确性和执行时间。

底部的组框中的部分是UserControl的两个实例,我将调用子控件。这对我来说很有意义,因为我知道我需要几个并且不想在主窗口中复制/粘贴集群。这就是麻烦发生的地方。

主窗口VM具有集合和切片范围的属性。我需要子VM才能访问这些属性。这证明是困难的。理想情况下,我想要这样的东西:

<local:AlgorithmTester RangeStart="{Binding RangeStart}"
                       RangeEnd="{Binding RangeEnd}"
                       Values="{Binding Values}" />

子VM具有自己的VM。如果我在控件上放置依赖属性,我必须将这些属性链接到控件的VM,可能是通过代码内绑定。这对我来说似乎有点不可思议。我想到了以下几种选择,但对我来说似乎都很奇怪:

  1. 不要使用UserControls;复制/粘贴每个测试人员的代码。
  2. 在代码中为测试人员设置VM,使lambda可以捕获闭包中所需的属性。 (现在这样做。)这让我觉得很脏,因为lambdas应该属于另一个班级。
  3. Twitter的朋友建议使用事件聚合器。子VM将订阅“RangeStartChanged”聚合事件(和其他人)。我不喜欢这样,因为我觉得子VM和父VM之间的关系应该更清楚;也许是旧习惯难以忍受?
  4. 对具有适当属性的类使用模板化的ItemsControl而不是UserControl。我仍然需要找到一种方法来传达主VM属性的变化,但这是主VM的内部逻辑。我不喜欢这种方法的唯一原因是它让我感到奇怪;这可能是多年的WinForms经验阻碍了我。 (我越喜欢这个,我就越喜欢它。)
  5. 还有其他解决方案吗?我对那些存在的东西过于挑剔吗?你会做什么?

2 个答案:

答案 0 :(得分:4)

我希望你的ParentViewModel包含ChildViewModel,并使用DataTemplate告诉WPF它应该使用local:AlgorithmTester控件

绘制ChildViewModel

示例ViewModel

public class ParentViewModel
{
    // Actual implementation omitted for sake of simplicity
    public ChildViewModel TestViewModelA { get; set; }
    public ChildViewModel TestViewModelB { get; set; }
}

示例XAML

<Window>
    <Window.Resources>
        <!-- Data Template to tell WPF how to draw the ViewModels -->
        <DataTemplate DataType="{x:Type local:ChildViewModel}">
            <local:AlgorithmTester />
        </DataTemplate>
    <Window.Resources>

    <!-- Put ViewModels in ContentControls and let WPF figure out how to display them -->
    <UniformGrid Columns="2">
        <ContentControl Content="{Binding TestViewModelA}" />
        <ContentControl Content="{Binding TestViewModelA}" />
    </UniformGrid>
</Window>

您的算法属性(StartRange,EndRange等)和方法(Calculate())存储在ChildViewModel中,AlgoritmTester是一个使ViewModel用户友好的视图。例如,它看起来像这样:

<UniformGrid Columns="2" Rows="4">
    <TextBlock Text="Minimum:" />
    <TextBox Text="{Binding StartRange}" />
    <TextBlock Text="Maximum:" />
    <TextBox Text="{Binding EndRange}" />
    <TextBlock Text="Elapsed:" />
    <TextBox Text="{Binding Elapsed}" />
    <Button Content="Calculate" Command="{Binding CalculateCommand}" />
</UniformGrid>

修改

关于上述问题的评论,您的ParentViewModel将负责将范围传递给ChildViewModels。

例如,如果范围是静态的,您可以在创建时设置它:

TestViewModelA = new ChildViewModel();
TestViewModelA.StartRange = 0;
TestViewModelA.EndRange = 10;

如果它是动态的,我会注册一个PropertyChanged事件处理程序来设置范围

void ParentViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "StartRange":
            TestViewModelA.StartRange = this.StartRange;
            TestViewModelB.StartRange = this.StartRange;
            break;
        case "EndRange":
            TestViewModelA.EndRange = this.EndRange ;
            TestViewModelB.EndRange = this.EndRange ;
            break;
    }
}

答案 1 :(得分:1)

我个人要么将它们作为具有依赖属性的UserControls,并将它们绑定在主视图模型中,如图所示。 Alternativley,以及我实际要做的是使用像Caliburn.Micro这样的MVVM框架,这使得视图合成非常容易。

在Caliburn.Micro的情况下,您的主视图模型上将有2个公共属性,每个属性类型为AlgorithmTesterViewModel,并且在主视图2 ContentControl上的名称与您的2个公共相同属性。

Caliburn.Micro将通过命名约定自动定位AlgorithmTesterView,将视图注入2 ContentControl(通过幕后的DataTemplates),并将每个控件绑定到属性在AlgorithmTesterViewModel上。

然后,您将在主视图模型中实例化两个AlgorithmTesterViewModel,传入适当的数据,并将它们分配给2个公共属性。

顺便说一下,你看起来很像David Mitchell。这既不是侮辱,也不是补充。只是观察事实。