鉴于任何具有5个以上视图和视图模型的中间MVVM应用程序,是否有任何关于如何构建此类应用程序的脚手架的推荐设计模式?
现在我通常有一个在App.OnStartup中创建的控制器:
我相信已有很好的设计模式,但我没有听说过或读过它们。
所以问题是: 是否有任何普遍接受的模式如何处理视图模型和视图的耦合(设置datacontext)和视图之间的导航?
在我看来,view-first(在XAML中设置DataContext)和ViewModel-First(让viewmodel获取通过DI / IOC注入的视图)都不是那么好,因为它们构成了view和viewmodel之间的依赖关系。 / p>
普通MVVM没有假设如何设置整个MVVM机器。 我只是想知道这个相当普遍的问题没有“现成的”解决方案。 我相信控制器被广泛使用。其他人如何解决这个问题?
答案 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 post中DataTemplateManager
的概念非常有趣。基本上,它允许你做像
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;
}
}