輕量級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完全可以!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM