在MVVM中如何将对象从父视图传递到子视图?

4

我目前正在尝试学习WPF和MVVM,但是最近遇到了一个问题,我不知道该如何解决。因为我是新手,所以如果有什么不太对的地方,请告诉我。

我有一个ParentView和它的ParentViewModel。ParentView包含两个视图SubViewA和SubViewB,它们都有自己的ViewModel。这是我的ParentView.xaml:

<local:ViewBase.DataContext>
    <local:ParentViewModel x:Name="Model" />
</local:ViewBase.DataContext>

<Grid>
    <local:SubViewA Visibility="{Binding ElementName=Model, Path=SubViewAVisibility}" />
    <local:SubViewB Visibility="{Binding ElementName=Model, Path=SubViewBVisibility}" />
</Grid>

我想做的是:SubViewModelB拥有一个绑定到SubViewB的属性。当ParentViewModel中发生某些事件时,我希望更改该属性。我认为这应该通过将SubViewModelB中的属性绑定到ParentViewModel中的属性来完成,但我不太确定如何实现?我在ParentView.xaml中尝试了以下内容:
<local:SubViewB Visibility="{Binding ElementName=Model, Path=SubViewBVisibility}" SomeProperty="{Binding ElementName=Model, Path=WhatIWantThePropertyToBe}" />

但是这并没有帮助我解决问题。那么该怎么办呢?我知道我可以通过MVVM Light工具包中的消息传递来解决,但这似乎不太适合我正在尝试做的事情。有什么建议吗?
谢谢。
2个回答

3
听起来EventAggregator模式是一个不错的解决方案。有一些很棒的实现,比如Microsoft Prism或者TinyMessenger(非常轻量级)。
作为代码示例,你可以像这样做(使用Prism,未经测试的代码)。
public class ParentViewModel
{
    private IEventAggregator eventAggregator;

    public ParentViewModel(IEventAggregator eventAggregator)
    {
       this.eventAggregator = eventAggregator;
    }

    public void PublishSomeEvent()
    {
        // When a condition occurs, publish an event any subscribers 
        // that may be listening
        this.eventAggregator.GetEvent<SomeEvent>()
            .Publish(new SomeEvent("Hello World!")));
    }
}

public class SubViewModel
{
    private IEventAggregator eventAggregator;

    public SubViewModel(IEventAggregator eventAggregator)
    {
       eventAggregator.GetEvent<SomeEvent>.SomeEvent(OnSomeEventOccurred);
    }

    public void OnSomeEventOccurred(SomeEvent arg)
    {
        // This method called when ParentViewModel publishes the event
        Console.WriteLine(arg.OptionalMessage);
    }
}

您需要一个单独的事件声明。例如,我使用以下内容
public SomeEvent : CompositePresentationEvent<SomeEvent>
{
    public SomeEvent(string optionalMessage)
    {
        this.optionalMessage = optionalMessage;
    }

    public string OptionalMessage { get { return optionalMessage; } } 
}

2
谢谢!我猜我可以用这种方式做,但这真的是正确的做法吗?对于一个实际上是耦合的问题,它似乎是一个非常解耦的解决方案。例如,如果我有多个ParentViews,每个都有自己的SubViews,这个解决方案行不通,对吧?不过我只有一个ParentView,所以我想这应该没问题 :-) 谢谢! - BlackWolf
这是正确的方法。仅仅因为它们是SubViews并不意味着它们需要知道它们的ParentView。对于多个ParentViews,您可以让ParentView向其SubViews传递Guid或某种标识符,该标识符也作为触发事件的一部分传递。然后,它们只需要检查该标识符以查看触发的事件是否适用于它们。 - Chris Shepherd
谢谢 Chris :) 是的,Mario 事件聚合器/代理是一个很棒的模式。结合依赖注入,它确实使生活变得轻松。如果你只想要轻量级的话,我会选择 TinyMessenger。它只有一个 cs 文件,我已经在生产系统中成功使用过了。 - Dr. Andrew Burnett-Thompson
好的,如果这是最佳实践,那我就采用它 :-) 已经在应用程序的其他地方使用了MVVM light messenger系统,所以也很容易实现 ;-) 谢谢大家的快速回复。 - BlackWolf
@MarioEmantsal 另一件你可以尝试的事情是使用 MultiBinding,但我不建议使用太多 Xaml 技巧来使你的应用程序在视觉上工作。最好在代码中明确表示,并在其周围添加一些单元测试。另一种方法是提取一个名为 VisibilityManager 的类,并将其作为依赖项(通过接口)注入到两个视图模型中,以根据逻辑/条件处理视图模型之间的可见性。 - Dr. Andrew Burnett-Thompson

1

你的ParentViewModel中会有一个SubViewModel实例,对吧?如果是这样的话,你可以在ParentViewModel中捕获事件,并手动更改SubViewModel的特定属性值。

我不确定这是否是完美的方法,但这应该能够解决问题。

另一种方法是直接将SubView的属性与ParentViewModel中的属性绑定。

试试这样:

Binding="{Binding RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type Window}}, Path=DataContext.ParentViewModelProperty}"

1
感谢您的回复。不幸的是,我没有直接访问SubViewModel的权限。我的视图自己创建它们的模型。我认为这是MVVM中适当的方式,也似乎是唯一的方式,在其中视图和其视图模型之间存在明确的关系。 - BlackWolf
1
谢谢。我采用了Messenger的方法,仍然很有帮助。 - BlackWolf

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