0

给定任何具有超过 5 个视图和视图模型的中间 MVVM 应用程序,是否有任何推荐的设计模式来说明如何为此类应用程序搭建脚手架?

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

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

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

所以问题是:是否有任何普遍接受的模式来处理视图模型和视图的耦合(设置数据上下文)以及视图之间的导航?

在我看来,视图优先(在 XAML 中设置 DataContext)和视图模型优先(让视图模型通过 DI/IOC 获得视图注入)都不是很好,因为它们在视图和视图模型之间建立了依赖关系。

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

4

3 回答 3

1

要考虑的一些设计模式是控制反转 (IoC)事件聚合器

对于 C# / MVVM,Caliburn 微框架(是其中之一)使 IoC 和事件聚合器更容易。

您正确地确定了 MVVM 的一个核心问题,即没有现成的解决方案可以真正将 ViewModel 与 View 解耦。ViewModel 是专门为与 Views 配对而构建的,这是一个核心概念。问题归结为如何管理 ViewModel / View 配对的实例。

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

首先查看模型很困难,因为它通常会导致破坏 MVVM 的主要租户之一——VM 应该可以在没有任何关联视图的情况下进行测试。

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

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

于 2013-03-28T18:46:40.577 回答
1

关于 View 和 ViewModel 之间的“链接”,我发现这篇文章DataTemplateManager中的概念非常有趣。基本上,它允许您执行以下操作

DataTemplateManager.Register<TViewModel1,TView1>();
DataTemplateManager.Register<TViewModel2,TView2>();
DataTemplateManager.Register<TViewModel3,TView3>();

诚然,它可能不是最好的解决方案,但非常方便。我已经将它整合到我自己自制的 MVVM 框架中。

于 2013-03-28T19:12:19.483 回答
1

我有一个小型项目,其中包含一个名为的单例类ViewFinder,它有几个名为MakeWindowFor(vm)and的静态方法MakeDialogFor(vm),它们都将视图模型作为参数。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;
    }
}
于 2013-03-28T20:02:28.227 回答