使用Caliburn.Micro从ViewModel调用UserControl的方法

3
我正在使用Caliburn.Micro编写一个基于ViewModel的MVVM应用程序。 我的View包含一个第三方UserControl,它实现了一个我想要/需要从相关的ViewModel中调用的方法。在遵守MVVM原则的同时,我该如何做到这一点?
在SO上有一个旧的thread,其中提出了一个类似的问题,但在更具体的上下文中进行了询问。如果有人能详细解释一下那里提出的方法,我将不胜感激。
方法一建议View可以订阅IEventAggregator消息。但是,我不得不使用代码后台文件来完成吗?(我认为这是MVVM中的大忌)
关于方法二,我不知道如何做。至于方法三,那是我首先尝试的,但不知何故,我没能使其正常工作。
1个回答

9
让我澄清一下您的理解:是的,通常应避免在代码后台中编写代码,但这只是因为MVVM使得将视图模型属性和命令绑定到视觉元素以便将其与幕后功能连接起来变得非常容易。
在视图的代码后台中编写特定于视图的代码是完全可以接受的,前提是它不越过关注点的边界。例如,我的应用程序中有一个视图对页面进行一些视觉处理,为此我需要在视图中编写代码。这段代码也可能与视图模型层交互,但它不会直接引用视图模型,因此保持了组件的松耦合。
如果您有需要调用特定方法的控件,则创建事件聚合器消息以将通知传播到视图是完全可以的,因为您仍然在视图模型和视图之间保持了关注点分离(并且应用程序组件保持封装和可测试性)。
示例视图(为了清晰起见,我省略了所有事件聚合器的连接代码和潜在的依赖项注入):
public class MyView : IHandle<SomeNotificationMessageType>
{
    // Handler for event aggregator messages of type SomeNotificationMessageType
    public void Handle(SomeNotificationMessageType message)
    {
        // Call a method on one of the page controls
        SomePageControl.SomeMethod();
    }
}

显然,在ViewModel中不会这样做:

很明显,你不会在ViewModel中这样做:

public class MyViewModel : IViewAware
{
    public void DoSomethingThatAffectsView()
    {
        var view = this.GetView() as MyView;

        view.SomePageControl.SomeMethod();
    }
}

这样做违反了MVVM原则,因为你将MyViewModel和MyView紧密耦合。

如果你想在caliburn micro中使用Context属性,允许同一个视图模型有多个视图呢?上面的代码就会失效 - 即使你检查了View类型,你仍然会得到一堆混乱的代码。

public class MyViewModel : IViewAware
{
    public void DoSomethingThatAffectsView()
    {
        var myview = this.GetView() as MyView;

        if(myview != null)        
            myview.SomePageControl.SomeMethod();

        var myotherview = this.GetView() as MyOtherView;

        if(myotherview != null)        
            myotherview.SomePageControl.SomeMethod();

        // ad infinitum...
    }
}

当然,这是主观的:可能你的用户控件以一种复杂的方式影响视图模型和视图,这种情况下,您可能需要考虑查看架构并找出如何使该用户控件更好地适应。
你了解 UC 是什么以及其方法的背景吗?

谢谢您的解释。我会采用EventAggregator的想法。我所说的UC是AvalonDock,我想做的是保存/加载布局。在1.*版本中有一个SaveLayout()方法;现在自2.0版本以来,显然必须将整个UC DockingManager传递给特殊的Serializer。 - PeterE
为了清晰起见,我把所有的依赖注入都省略了,但现在不太清楚了。 :) 我能想到的唯一获取事件聚合器的方法是在我的视图的无参构造函数中使用 IoC.Get<IEventAggregator>()。有没有更好的依赖注入方式,而不仅仅是直接从容器中获取对象? - Anton Kedrov
你的视图也可以参与依赖注入 - 只需将其添加到构造函数中即可。 - Charleh
如果我这样做,那么我将会得到一个运行时异常:"没有为这个对象定义无参构造函数"。或者是我错过了什么?到目前为止,我已经在ViewModel中的OnViewAttached()方法内调用了eventAggregator.Subscribe(view),我认为这比在View构造函数中使用IoC.Get<IEventAggregator>()更好。 - Anton Kedrov

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