驱动MVVM应用程序

时间:2013-03-28 18:36:25

标签: c# design-patterns mvvm architecture

鉴于任何具有5个以上视图和视图模型的中间MVVM应用程序,是否有任何关于如何构建此类应用程序的脚手架的推荐设计模式?

现在我通常有一个在App.OnStartup中创建的控制器:

  • 设置主视图
  • 注入子视图(通常我有一个带有状态栏和导航的MainWindow,它有“内部窗口”)
  • 处理视图和视图模型的结合。
  • 处理导航(从视图A到视图B)
  • 支持痕迹导航(以及类似典型的NavigationService.GoBack())

我相信已有很好的设计模式,但我没有听说过或读过它们。

所以问题是: 是否有任何普遍接受的模式如何处理视图模型和视图的耦合(设置datacontext)和视图之间的导航?

在我看来,view-first(在XAML中设置DataContext)和ViewModel-First(让viewmodel获取通过DI / IOC注入的视图)都不是那么好,因为它们构成了view和viewmodel之间的依赖关系。 / p>

普通MVVM没有假设如何设置整个MVVM机器。 我只是想知道这个相当普遍的问题没有“现成的”解决方案。 我相信控制器被广泛使用。其他人如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

要考虑的一些设计模式是Inversion of Control (IoC)Event Aggregator

对于C#/ MVVM,Caliburn Micro Framework(是其中之一)使IoC和Event Aggregator更容易。

您正确地确定了MVVM的核心问题,因为没有现成的解决方案可以真正地将ViewModel与View分离。这是一个核心概念,ViewModels的目的是为了与View一起削减。问题归结于如何管理ViewModel / View配对的实例。

View第一种方法假设View知道并且可以根据需要实例化ViewModels - 这是SoC的一个问题,因为任何View类现在都有多个职责;启动ViewModel并处理UI。

首先查看模型很困难,因为它经常导致破坏MVVM的主要部分之一 - 虚拟机应该是可测试的,没有任何相关的视图。

这是IoC的用武之地。 IoC通常驻留在View层中(这是为了允许它根据需要访问所有View和ViewModel类)它不必是View本身。将IoC管理器视为控制器通常会更好 - 哪种导致MVCVM的伪模式。这个“控制器”的唯一目的就是为需要它的人提供View和ViewModel实例配对。

事件聚合器模式确实对此有所帮助,因为ViewModel和View中的类不再需要担心与谁配对,并且只能在自己的级别中与其他类进行交互。特定的视图模型无需关心谁发送了事件“更新加载进度”,只需要通过设置它的进度属性来负责处理事件的结果。

答案 1 :(得分:1)

关于"链接"在View和ViewModel之间,我发现this postDataTemplateManager的概念非常有趣。基本上,它允许你做像

这样的事情
DataTemplateManager.Register<TViewModel1,TView1>();
DataTemplateManager.Register<TViewModel2,TView2>();
DataTemplateManager.Register<TViewModel3,TView3>();
不可否认,它可能不是最好的解决方案,但非常方便。我已将它整合到我自己的自制MVVM框架中。

答案 2 :(得分:1)

我有一个小项目,其中包含一个名为ViewFinder的单例类,它有一些名为MakeWindowFor(vm)MakeDialogFor(vm)的静态方法,它们都将viewmodel作为参数。 ViewFinder有一个我填写的词典链接视图模型与我设置的窗口对应它们。可以添加更多信息,因为可能视图存在于另一个视图中,而不仅仅是一个窗口。

这可能不是完成任务的最佳方式,但可以满足我对此项目的需求,并使视图模型不会意识到实际的视图实现。我所有视图模型的祖先都包含显示消息框之类的事件,而我的所有窗口都来自知道如何订阅和响应这些常见事件的基类。

public class ViewFinder {
    private static ViewFinder m_Instance;
    public static ViewFinder Instance {
        get {
            if (m_Instance == null)
                m_Instance = new ViewFinder();
            return (m_Instance);
        }
    }

    /// Maps viewmodels to windows/dialogs. The key is the type of the viewmodel, the value is the type of the window.
    private Dictionary<Type, Type> ViewDictionary = new Dictionary<Type, Type>();

    /// Private constructor because this is a singleton class.
    ///
    /// Registers the viewmodels/views.
    private ViewFinder() {
        Register(typeof(SomeViewModel), typeof(SomeWindowsForViewModel));
        Register(typeof(SomeViewModel2), typeof(SomeWindowsForViewModel2));
    }

    /// Registers a window with a viewmodel for later lookup.
    /// <param name="viewModelType">The Type of the viewmodel. Must descend from ViewModelBase.</param>
    /// <param name="windowType">The Type of the view. Must descend from WindowBase.</param>
    public void Register(Type viewModelType, Type windowType) {
        if (viewModelType == null)
            throw new ArgumentNullException("viewModelType");
        if (windowType == null)
            throw new ArgumentNullException("windowType");
        if (!viewModelType.IsSubclassOf(typeof(ViewModelBase)))
            throw new ArgumentException("viewModelType must derive from ViewModelBase.");
        if (!windowType.IsSubclassOf(typeof(WindowBase)))
            throw new ArgumentException("windowType must derive from WindowBase.");
        ViewDictionary.Add(viewModelType, windowType);
    }

    /// Finds the window registered for the viewmodel and shows it in a non-modal way.
    public void MakeWindowFor(ViewModelBase viewModel) {
        Window win = CreateWindow(viewModel);
        win.Show();
    }

    /// Finds a window for a viewmodel and shows it with ShowDialog().
    public bool? MakeDialogFor(ViewModelBase viewModel) {
        Window win = CreateWindow(viewModel);
        return (win.ShowDialog());
    }

    /// Helper function that searches through the ViewDictionary and finds a window. The window is not shown here,
    /// because it might be a regular non-modal window or a dialog.
    private Window CreateWindow(ViewModelBase viewModel) {
        Type viewType = ViewDictionary[viewModel.GetType()] as Type;
        if (viewType == null)
            throw new Exception(String.Format("ViewFinder can't find a view for type '{0}'.", viewModel.GetType().Name));
        Window win = Activator.CreateInstance(viewType) as Window;
        if (win == null)
            throw new Exception(String.Format("Activator returned null while trying to create instance of '{0}'.", viewType.Name));
        win.DataContext = viewModel;
        return win;
    }
}