原文链接ViewModel First
ViewModel-first
方法对 Stylet 的架构至关重要,但如果您以传统的 View-first
方式学习 MVVM
,则不直观。
希望这篇文章能让一切都清楚。
让我们从定义视图优先的方法开始,我的意思是什么。MVVM 声明 ViewModel 应该对 View 一无所知,但 View 应该知道 ViewModel。那么,附加 View 和 ViewModel 的明显方法是让 View 在其代码隐藏中构造其 ViewModel - 如下所示:
public partial class MyView : Window
{
public MyView()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
视图可以创建和拥有其他视图,这意味着您可以将视图组合成层次结构。
当你组合了几个视图时,关键就来了,比如这样说,其中一个外壳包含一个顶栏和一个框架,在其中可以显示任何页面:
<Window x:Class="MyNamespace.ShellView" ....>
<StackPanel>
<my:TopBarView/>
<Frame x:Name="navigationFrame"/>
StackPanel>
Window>
TopBarView
有自己的视图模型TopBarViewModel
…非常好。
现在假设TopBarView
有一个字段包含您要更新的一些数据,例如当前页面的标题。现在,theShellViewModel知道这一点(毕竟它决定了当前页面是什么),但TopBarViewModel
不知道(它会怎样?它什么都不知道)。诱惑是在TopBarView
上公开一个依赖属性并将其绑定到 中ShellViewModel
,如下所示:
<Window x:Class="MyNamespace.ShellView" .... x:Name="rootObject">
<StackPanel>
<my:TopBarView CurrentPageTitle="{Binding CurrentPageTitle, ElementName=rootObject}"/>
<Frame x:Name="navigationFrame"/>
StackPanel>
Window>
但这很讨厌。您现在已经获得了绑定到ShellViewModel
.
另一个主要问题是显示窗口和对话框。在传统的 MVVM 中,这有点痛苦。一种选择是从 ViewModel 内部实例化和显示视图(使用Show()
或者 ShowDialog()
)(这使得它,或者至少它的那一点,不可测试)。更好的选择是在 View 的代码隐藏中实例化您想要显示的 View,并从那里显示它。这意味着您需要建立告诉 View 显示此对话框的方法,以及将对话框的结果返回给 ViewModel
的方法。
实际上,为Frame
上述设置内容需要实例化一个 View 以放入其中。这有同样的困境——要么是 ViewModel 实例化它(使其无法测试),要么是 View 实例化它(导致沟通痛苦)。
无论哪种方式,这种方法都有一些讨厌的地方。
ViewModel-first
方法接受 ViewModel
不应该知道任何关于其 View 的信息,但不接受 View 也应该负责构造 ViewModel。相反,第三个服务负责为给定的 ViewModel 定位正确的 View,并正确设置其 DataContext
。
默认实现使用命名约定为给定的 ViewModel 找到正确的 View,将其名称中的“ViewModel”替换为“View”。这在ViewManager中有更详细的解释。
这允许 ViewModel 由其他 ViewModel 创建。这允许 ViewModel 了解并拥有其他 ViewModel。这使您可以正确组合 ViewModel。
这个技巧还有另一部分,最好用例子来解释:
public class ShellViewModel
{
public TopBarViewModel TopBar { get; private set; }
// Stuff to instantiate and assign TopBarViewModel
}
<Window x:Class="MyNamespace.ShellView"
xmlns:s="https://github.com/canton7/Stylet" .....>
<StackPanel>
<ContentControl s:View.Model="{Binding TopBar}"/>
StackPanel>
Window>
该View.Model
附加属性将获取其绑定到(在这种情况下,它的一个实例的视图模型TopBarViewModel
),并找到正确的观点(TopBarView
)。它将实例化一个实例,并将其设置为那个的内容ContentControl
。
结果是TopBarView
可以从其 中获取当前页面的名称TopBarViewModel
,并且TopBarViewModel
可以通过 告诉它ShellViewModel
。问题解决了!
该ContentControl
技巧也适用于导航:
<Window x:Class="MyNamespace.ShellView"
xmlns:s="https://github.com/canton7/Stylet" .....>
<StackPanel>
<ContentControl s:View.Model="{Binding TopBar}"/>
<ContentControl s:View.Model="{Binding CurrentPage}"/>
StackPanel>
Window>
然后ShellViewModel将通过实例化该页面的新实例ViewModel并将其分配给属性来导航到新页面CurrentPage。请注意如何ShellViewModel不再需要了解有关视图的任何信息。它不必实例化单个视图。这是一个非常重要、有用和强大的观点。
然后,将ShellViewModel
通过实例化该页面的新实例ViewModel
并将其分配给属性来导航到新页面CurrentPage
。请注意如何ShellViewModel
不再需要了解有关视图的任何信息。它不必实例化单个视图。这是一个非常重要、有用和强大的观点。
The WindowManager以几乎相同的方式处理对话框和窗口。这采用给定的 ViewModel 实例,并将其视图显示为对话框或窗口。
有了这种方法,您实际上不需要在代码隐藏中执行任何操作。您当然可以这样做,但是几乎没有什么是您无法通过Actions(用于处理事件)、转换器、附加属性和(最重要的)附加行为来解决的。
Stylet 允许您完全删除代码隐藏(它会为你调用InitializeComponent
),我们强烈建议您这样做。删除代码隐藏!
注意*:如果您使用的是 VB.NET,有时您的 XAML 命名空间会在您删除代码隐藏后停止工作。如果是这种情况,只需使用匹配的文件名重新创建代码隐藏,为其提供正确的命名空间和类,然后将其余部分留空。例如MyView.xaml.vb
:
Namespace Views
Public Class MyView
End Class
End Namespace
项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/ViewModel-First
上一篇:WPF的MVVM框架Stylet开发文档 3. Bootstrapper引导程序
下一篇:WPF的MVVM框架Stylet开发文档 5. Actions