【问题标题】:Driving a MVVM application驱动 MVVM 应用程序
【发布时间】:2013-03-19 08:27:54
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • 我看不到你在这里问什么。
  • 他正在询问在他的应用程序中实现 MVVM 模式的最佳方式。
  • 为了更清楚而编辑的问题
  • 为什么需要将视图与视图模型解耦?你期待什么样的设计?

标签: c# design-patterns mvvm architecture


【解决方案1】:

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

对于 C# / MVVM,Caliburn Micro Framework(是其中之一)使 IoC 和事件聚合器变得更加容易。

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

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

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

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

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

【讨论】:

  • 是的,Caliburn 引入了一种全新的方式来处理此类场景,并为 EventToCommand 等其他问题提供了一些支持(不知道 Caliburn 中的确切名称)。 IoC 始终是一个问题,即使在简单的应用程序中也是如此。
  • Prism Eventtaggregator 看起来很有趣,也许这是任何“控制器”/“演示者”/“信使”都应该具备的。
  • @MareInfinitus 我从未使用过 PRISM,但我听说它也为 Egent Aggregator 和 IoC 模式提供了一个很好的框架。一旦我开始使用事件聚合器模式,我就再也没有回头……
  • 你是对的。我在 Caliburn 上玩了两个小时,现在有了一个应用程序,如果一切都是自己完成的话,那是一天的工作。现在我从 eventtaggregator 开始,这是自己做的麻烦。我已经开始喜欢它了;)
【解决方案2】:

关于 View 和 ViewModel 之间的“链接”,我发现this post 中的DataTemplateManager 的概念非常有趣。基本上,它允许你做类似的事情

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

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

【讨论】:

  • 有趣的技术。将在我的控制器中尝试那个;)
【解决方案3】:

我有一个小型项目,其中包含一个名为ViewFinder 的单例类,它有两个名为MakeWindowFor(vm)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;
    }
}

【讨论】:

  • 所以您在代码隐藏中对“ShowMessageBox”之类的事件做出反应,而您的视图模型本身不显示消息框?
  • 是的。我的ViewModelBase 祖先类实现了像RaiseMessageBoxEvent() 这样的函数,它使用了一个自定义的EventArgs。然后,我所有的窗口/对话框都来自 WindowBase 类,该类自动订阅这些常见事件并做出反应。视图具有 using 语句来将视图模型纳入范围,但反之则不然。视图订阅它们自己的 DataContextChanged 事件以确保它取消订阅之前的事件。我提到的 ViewFinder 类就像一个将两者连接在一起的编程配置文件。
  • 您的取景器很棒!目前我正在玩 caliburn,这似乎带来了很多我真正错过的东西。
猜你喜欢
  • 1970-01-01
  • 2022-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-13
  • 1970-01-01
  • 2020-12-12
  • 2011-10-02
相关资源
最近更新 更多