zoukankan      html  css  js  c++  java
  • 轻量级MVVM框架Stylet介绍:(4) ViewModel优先

    ViewModel-first方法对Stylet的架构至关重要,但如果你以传统的View-first方式学习MVVM,那么这种方法就不直观了。

    希望本文能把一切都说清楚。

    视图优先方法

    让我们从定义视图优先方法开始。MVVM 声明 ViewModel 应该对 View 一无所知,反过来说View应该知道 ViewModel。将View与ViewModel绑定在一起的最简单方式是将ViewModel放置在View的Codebehind里,类似下面的代码:

    public partial class MyView : Window
    {
       public MyView()
       {
          InitializeComponent();
     
          this.DataContext = new MyViewModel();
       }
    }
    

    当然视图还可以创建和拥有其他视图,可以将多个视图构成视图树,所有这些都还好。

    但是像下面这样的情况,

    <!-- This is a window which contains a top bar and another page -->
    <Window x:Class="MyNamespace.ShellView" ....>
       <StackPanel>
          <my:TopBarView/>
          <Frame x:Name="navigationFrame"/>
       </StackPanel>
    </Window>
    

    这里的TopBarView有其ViewModel,TopBarViewModel。

    假定TopBarView里有一个字段的数据想要去更新,比如当前页面的标题。现在,ShellViewModel知晓哪一个Page是当前页面,但是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>
    

    这真的不够优雅。

    另一个主要问题是显示窗口和对话框。在传统的MVVM中,这有点痛苦。一种选择是从 ViewModel 内部实例化和显示 View(using Show()或 ShowDialog()),这使其或至少其中的一部分无法测试)。更好的选择是在视图的codebehind中实例化,然后在那里显示。这意味着您需要建立告诉View显示此对话框的方法,以及将对话框的结果返回到 ViewModel 的方法。

    实际上,设置上述Frame内容需要实例化视图以放入其中。这具有相同的困境 - 要么 ViewModel 实例化它(使其不可测试),要么在视图实例化它(导致通信痛苦)。

    无论哪种方式,这种方法都不太优雅。

    ViewModel优先的实践

    ViewModel优先的模式使得ViewModel与View相互之间独立存在,实现了完美的分离。取而代之的是采用第三方的服务来建立View与ViewModel之间的关系,配置其相应的DataContext。

    默认的实现是使用命名约定来建立联系,对于一个给定的ViewModel,将其变量名中的“ViewModel”替换为“View”即可。更多细节参见ViewManager。

    这使得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附加属性从其ViewModel的绑定中获取ViewModel(此例中是TopBarViewModel的一个实例),然后定位到正确的View上(TopBarView)。通过这种方式实例化,将内容设置到ContentControl中。

    此例中,TopBarView即可以从其TopBarViewModel中获取当前页面的名称,也可通过ShellViewModel获得页面名称的通知,问题得到了解决!

    同样,ContentControl在Navigation中也工作得很好:

    <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不再需要知道任何关于视图(views)的信息,没必要再去实例化一个单独的view了,这一点非常重要,也非常有用。

    对话框(Dialogs)和窗体(Windows)也可以通过WindowManager用同样的方法处理。只需要传递给出的ViewModel实例,对话框或窗体的View就会显示出来。

    删除Code-Behind!

    通过这一系列操作,没必要再写codebehind的代码了。通过使用Actions(处理事件),Converters,附加属性和附件行为,删除Code-Behind完全可以!

  • 相关阅读:
    读理,妙句秒人秒事,二记
    读理,妙句秒人秒事,一记
    (原)DirectX11 深度测试(有点另类)
    游戏编程书籍推荐
    android入门-环境搭建
    Spring||Interview
    JSR-133内存模型手册
    JVM执行引擎
    HIbernate总结
    虚拟机类加载机制
  • 原文地址:https://www.cnblogs.com/qouoww/p/15786060.html
Copyright © 2011-2022 走看看